Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
148 commits
Select commit Hold shift + click to select a range
2e6723b
feat(thruster-car): add the ids, theme and layout
veelenga Oct 17, 2026
20ad00d
feat(thruster-car): add the cold-gas run model
veelenga Oct 17, 2026
8cc143f
feat(thruster-car): add the assembly contract and placeholder
veelenga Oct 17, 2026
3656569
feat(thruster-car): add the playback, plume and timeline
veelenga Oct 17, 2026
2874857
feat(thruster-car): add the store, presets and live readings
veelenga Oct 17, 2026
863c35c
feat(thruster-car): add the part labels, cover and social card
veelenga Oct 17, 2026
a1543bb
feat(thruster-car): add the dock, gauge readouts and chapter actions
veelenga Oct 17, 2026
8004cae
feat(thruster-car): add the chapter calculators and readouts
veelenga Oct 17, 2026
a1528aa
test(thruster-car): cover the store, presets and live readings
veelenga Oct 17, 2026
1ff3878
feat(thruster-car): add the follow and wide camera views
veelenga Oct 17, 2026
943a6fd
test(thruster-car): cover the dock, gauge readouts and chapter widgets
veelenga Oct 17, 2026
86ff408
feat(thruster-car): add the scene controller and store bindings
veelenga Oct 17, 2026
7ee2cec
feat(thruster-car): register the thruster car explainer
veelenga Oct 17, 2026
5b54144
feat(thruster-car): write the English chapters and copy
veelenga Oct 17, 2026
be29ffc
feat(thruster-car): translate the copy into Portuguese
veelenga Oct 17, 2026
9b9eb63
feat(thruster-car): translate the copy into Spanish
veelenga Oct 17, 2026
531ddd7
feat(thruster-car): translate the copy into French
veelenga Oct 17, 2026
3340b93
feat(thruster-car): translate the copy into Chinese
veelenga Oct 17, 2026
2fd8a77
feat(thruster-car): translate the copy into Ukrainian
veelenga Oct 17, 2026
605305a
feat(thruster-car): translate the copy into German
veelenga Oct 17, 2026
3e294f2
feat(thruster-car): translate the copy into Japanese
veelenga Oct 17, 2026
bd3d234
fix(thruster-car): own the tank cut-off in the copy
veelenga Oct 17, 2026
57b928b
fix(thruster-car): add a fill pressure curve and use the exact shell …
veelenga Oct 17, 2026
ada4d1c
fix(thruster-car): step the launch speed by 0.1 from 13.3 m/s
veelenga Oct 17, 2026
1edbd01
fix(thruster-car): read the fill pressure from the 300 K fill curve
veelenga Oct 17, 2026
7bdd390
fix(thruster-car): describe each phase and seek to the run's moments
veelenga Oct 17, 2026
d0e769b
fix(thruster-car): keep one newtons per kilonewton constant
veelenga Oct 17, 2026
b909675
fix(thruster-car): type the tank size as the two choices
veelenga Oct 17, 2026
12612e4
fix(thruster-car): keep the live reading cache per store
veelenga Oct 17, 2026
cfd2958
feat(thruster-car): turn the ramp into a hook and chain the sprint, h…
veelenga Oct 17, 2026
2861386
fix(thruster-car): follow the renamed hook preset and the new phases
veelenga Oct 17, 2026
462a271
feat(thruster-car): park four cars and a truck in the jump row
veelenga Oct 17, 2026
bb4cac0
test(thruster-car): follow the hook ramp numbers
veelenga Oct 17, 2026
2614a0f
fix(thruster-car): apply the review to the English copy
veelenga Oct 17, 2026
537121a
fix(thruster-car): apply the review to the translations
veelenga Oct 17, 2026
16a5fd6
feat(thruster-car): name the Tesla Roadster demo in the head copy
veelenga Oct 17, 2026
d5b1e4d
feat(thruster-car): write the three tests into the English copy
veelenga Oct 17, 2026
247f3d9
feat(thruster-car): write the three tests into the translations
veelenga Oct 17, 2026
7ef1bf1
feat(thruster-car): work out the hook loop from the entry speed
veelenga Oct 17, 2026
af6ef74
feat(thruster-car): start the hook in the settle and label the jump arc
veelenga Oct 17, 2026
d802bf0
refactor(thruster-car): drop the ballistic hop and name the deck height
veelenga Oct 17, 2026
03c50dd
feat(thruster-car): chase the roll from behind as the car heads back
veelenga Oct 17, 2026
2e0186b
feat(thruster-car): work out the jump from its height
veelenga Oct 17, 2026
da04b57
feat(thruster-car): keep the camera and fog limits beyond the site fr…
veelenga Oct 17, 2026
959fd71
fix(thruster-car): scale the thrust gauge to the sprint
veelenga Oct 17, 2026
d5c8b38
feat(thruster-car): label the parked row on the left
veelenga Oct 17, 2026
56f4341
test(thruster-car): keep the car centre in front of the follow cameras
veelenga Oct 17, 2026
f8c2139
fix(thruster-car): quote the final gas budget
veelenga Oct 17, 2026
6e44bd5
fix(thruster-car): match the sample values and move the chips up
veelenga Oct 17, 2026
6779403
fix(thruster-car): point at the chips above
veelenga Oct 17, 2026
2799aee
feat(thruster-car): add the scene part context and views
veelenga Oct 17, 2026
31270ce
feat(thruster-car): add the drawn car dimensions
veelenga Oct 17, 2026
497a9e8
feat(thruster-car): add the dusk scene options and lighting
veelenga Oct 17, 2026
e851f91
feat(thruster-car): add the site and car regions
veelenga Oct 17, 2026
3e43c3e
feat(thruster-car): add the cold-gas plumes
veelenga Oct 17, 2026
d5c3bcb
feat(thruster-car): add the force arrows
veelenga Oct 17, 2026
7e55c16
feat(thruster-car): add the effects part with fan glow, shadow and gh…
veelenga Oct 17, 2026
cd00338
feat(thruster-car): thicken the condensation plumes and soften the fa…
veelenga Oct 17, 2026
1ea708d
feat(thruster-car): add the site, twisted ramp and support geometry
veelenga Oct 17, 2026
3b62c03
feat(thruster-car): add the lofted car body geometry
veelenga Oct 17, 2026
fd0e9ad
feat(thruster-car): add the forged wheels with brakes and a speed blur
veelenga Oct 17, 2026
3e3d69a
feat(thruster-car): add the wound air tank, its gas core and the valv…
veelenga Oct 17, 2026
587955e
feat(thruster-car): add the nozzle bells with frost and direction cones
veelenga Oct 17, 2026
ae45105
feat(thruster-car): add the car body part and finishes
veelenga Oct 17, 2026
597b0dc
feat(thruster-car): give the car a gloss black tail panel and a softe…
veelenga Oct 17, 2026
859eb63
feat(thruster-car): space the ramp portals and soften the scrub texture
veelenga Oct 17, 2026
7203813
feat(thruster-car): add the dusk world with sky, runway and twisted ramp
veelenga Oct 17, 2026
e71d317
refactor(thruster-car): move the side mirror geometry into its own mo…
veelenga Oct 17, 2026
cc0bca3
feat(thruster-car): add the car cabin
veelenga Oct 17, 2026
f031ab6
refactor(thruster-car): name the site surface tuning and raise the ra…
veelenga Oct 17, 2026
a492679
feat(thruster-car): bring the front nozzle mouths out to the nose skin
veelenga Oct 17, 2026
050011e
feat(thruster-car): compose the car part with body, cabin, wheels, ta…
veelenga Oct 17, 2026
a6014fc
refactor(thruster-car): drop the unused cabin finishes
veelenga Oct 17, 2026
4bdf302
feat(thruster-car): compose the car, world and effects into the assembly
veelenga Oct 17, 2026
3be206a
feat(thruster-car): draw the plumes as one car-frame cloud per nozzle…
veelenga Oct 17, 2026
b54a397
refactor(thruster-car): reuse scratch objects in the effects frame up…
veelenga Oct 17, 2026
7c19aee
feat(thruster-car): add a swatch palette so one material carries many…
veelenga Oct 17, 2026
6fe9dbe
perf(thruster-car): draw the car body and cabin in seven meshes
veelenga Oct 17, 2026
f4c3b08
test(thruster-car): check the grouped plume clouds in the assembly
veelenga Oct 17, 2026
f11059c
perf(thruster-car): draw the four wheels as instanced pieces
veelenga Oct 17, 2026
acc928c
perf(thruster-car): merge the tank and valve meshes per material
veelenga Oct 17, 2026
9f90ab6
perf(thruster-car): instance the nozzle bells, frost and cones per group
veelenga Oct 17, 2026
4c7d12c
fix(thruster-car): darken the wheel barrels so they stop glaring
veelenga Oct 17, 2026
a97d648
feat(thruster-car): reflect a dusk sky in the car paint, glass and trim
veelenga Oct 17, 2026
63aad66
feat(thruster-car): reshape the car as a low mid-engine coupe with re…
veelenga Oct 17, 2026
bf9cae1
feat(thruster-car): reflect the dusk sky in the wheel, valve and nozz…
veelenga Oct 17, 2026
ffdebfc
feat(thruster-car): add loft, section and wheel helpers for the parke…
veelenga Oct 17, 2026
df2a196
feat(thruster-car): add grass tufts, dirt tracks and scrub cards to t…
veelenga Oct 17, 2026
4696615
feat(thruster-car): brighten the dusk horizon and matte the scrub ground
veelenga Oct 17, 2026
7f791b8
feat(thruster-car): billow the condensation plumes, roll a ground haz…
veelenga Oct 17, 2026
26edf91
fix(thruster-car): colour the rear nozzle cones with the sprint tone
veelenga Oct 17, 2026
8042968
feat(thruster-car): replace the corkscrew with the hook ramp on a ste…
veelenga Oct 17, 2026
f2ce123
feat(thruster-car): add the jump region and check the regions on the …
veelenga Oct 17, 2026
c4e11f5
feat(thruster-car): space the hook portals and brace them across the …
veelenga Oct 17, 2026
a8a1a6e
feat(thruster-car): widen the fog and zoom range for the 165 m site view
veelenga Oct 17, 2026
d63248e
feat(thruster-car): warm the dusk key and lift the violet fill
veelenga Oct 17, 2026
a316b3f
feat(thruster-car): brace the hook truss with gusseted steel and a ga…
veelenga Oct 17, 2026
177ead0
feat(thruster-car): add the round body, cabin and car builders for th…
veelenga Oct 17, 2026
03514e6
feat(thruster-car): add the pickup and tractor unit builders for the …
veelenga Oct 17, 2026
62a9b0a
feat(thruster-car): draw the parked row in one palette mesh with soft…
veelenga Oct 17, 2026
28ad7f6
feat(thruster-car): add the parked row to the assembly and test the n…
veelenga Oct 17, 2026
ff54ed6
perf(thruster-car): build the ghost silhouette as a coarse low-poly car
veelenga Oct 17, 2026
0f228f4
feat(thruster-car): trace the hook and jump ghost arcs with instanced…
veelenga Oct 17, 2026
88c964a
fix(thruster-car): bake the contact shadow pad as a data texture with…
veelenga Oct 17, 2026
ad80ec7
feat(thruster-car): keep the sprint wake and ground haze low
veelenga Oct 17, 2026
b92a37b
fix(thruster-car): move the corner and front nozzles out to the body …
veelenga Oct 17, 2026
a693aa5
refactor(thruster-car): take the plume length rule from the plume model
veelenga Oct 17, 2026
dc95114
feat(thruster-car): mount the real scene assembly with its dusk light
veelenga Oct 17, 2026
29d5eb2
fix(thruster-car): drop the twist ghost that sat on top of the apex one
veelenga Oct 17, 2026
cedf78a
fix(thruster-car): push the dusk fog out past the site wide view
veelenga Oct 17, 2026
1aaea04
fix(thruster-car): start the jump chapter just before lift-off and ti…
veelenga Oct 17, 2026
2ea36e0
fix(thruster-car): frame the wide views low on the ramp, jump and sit…
veelenga Oct 17, 2026
29e160f
feat(thruster-car): hold the car upside down on the hook before the p…
veelenga Oct 17, 2026
b4372cd
fix(thruster-car): follow the hold phases
veelenga Oct 17, 2026
afd5484
test(thruster-car): follow the hold numbers
veelenga Oct 17, 2026
ccbbbc2
fix(thruster-car): final English reader pass
veelenga Oct 17, 2026
efdec85
fix(thruster-car): final native-reader pass on the translations
veelenga Oct 17, 2026
fdfa227
feat(thruster-car): tell the hold on the hook in the English copy
veelenga Oct 17, 2026
b413740
feat(thruster-car): tell the hold on the hook in the translations
veelenga Oct 17, 2026
2087d00
fix(thruster-car): quote the gas budget after the hold
veelenga Oct 17, 2026
07c6f87
test(thruster-car): follow the hold timing in the scene and widget tests
veelenga Oct 17, 2026
3f6a039
refactor(thruster-car): drop the unused site fog distance
veelenga Oct 17, 2026
dca675c
fix(thruster-car): frame the hold on the hook side-on at deck height
veelenga Oct 17, 2026
6f83ae2
fix(thruster-car): hand the tank close-up to the side view once the r…
veelenga Oct 17, 2026
b43b5fc
perf(thruster-car): share site geometry and texture builders
veelenga Oct 17, 2026
1fa2a85
perf(thruster-car): fold site noise octaves into one helper
veelenga Oct 17, 2026
ba8844e
test(thruster-car): give the assembly controller tests room under ful…
veelenga Oct 17, 2026
b715f38
fix(thruster-car): count the car's weight in the loop load and cut th…
veelenga Oct 17, 2026
0514d9d
fix(thruster-car): apply the second review to the English copy
veelenga Oct 17, 2026
8906afb
fix(thruster-car): apply the second review to the translations
veelenga Oct 17, 2026
fddce30
build: raise the page JavaScript budget by 10 KB for richer scenes
veelenga Oct 17, 2026
eefc054
fix(thruster-car): roll the wheels by the car's own travel after the …
veelenga Oct 17, 2026
b5e0d3d
perf(thruster-car): drop per-frame allocations and double layouts in …
veelenga Oct 17, 2026
e22feb6
fix(thruster-car): free every instanced mesh on dispose
veelenga Oct 17, 2026
fc375dc
refactor(thruster-car): share the turn constants and duplicated helpers
veelenga Oct 17, 2026
7ff4c81
refactor(thruster-car): drop dead exports
veelenga Oct 17, 2026
f88d687
feat(thruster-car): add the cover and social images
veelenga Oct 17, 2026
8a04dac
fix(thruster-car): shorten the phase names for the strip
veelenga Oct 17, 2026
5009e88
feat(tesla-flying-roadster): rename the explainer slug for discoverab…
veelenga Oct 17, 2026
6e6c78b
fix(tesla-flying-roadster): describe the bronze car in the social alt
veelenga Oct 17, 2026
e85d04d
fix(tesla-flying-roadster): render the social card for the renamed slug
veelenga Oct 17, 2026
bc4fa5d
fix(tesla-flying-roadster): native-reader verification of every language
veelenga Oct 17, 2026
b2ba5ec
fix(tesla-flying-roadster): open the flip chapter with the car clear …
veelenga Oct 17, 2026
b8ad655
fix(tesla-flying-roadster): set the car in by the wheelbase chord on …
veelenga Oct 17, 2026
b59d553
fix(tesla-flying-roadster): keep the speed blur a faint matte veil ov…
veelenga Oct 17, 2026
987b08d
feat(tesla-flying-roadster): use the flip frame for the cover and card
veelenga Oct 17, 2026
621094e
fix(tesla-flying-roadster): fit the Ukrainian phase labels in the strip
veelenga Oct 17, 2026
3917b95
fix(tesla-flying-roadster): call the Ukrainian run-up Розгін and the …
veelenga Oct 17, 2026
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
723 changes: 723 additions & 0 deletions explainers/tesla-flying-roadster/chapters.html

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions explainers/tesla-flying-roadster/explainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"slug": "tesla-flying-roadster",
"tags": ["vehicles", "physics", "flight"],
"topic": "cold-gas thruster car",
"cover": "cover.webp",
"entry": "src/index.ts",
"chapters": "chapters.html",
"locales": ["en", "zh", "es", "uk", "pt", "fr", "de", "ja"],
"social": {
"image": "social/og-image.png",
"alt": "A grey single-seat sports car upside down above a curled ramp at dusk, white gas jets at its corners, beside the title How a cold-gas thruster hops a car"
}
}
308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/de.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/en.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/es.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/fr.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/ja.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/pt.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/uk.json

Large diffs are not rendered by default.

308 changes: 308 additions & 0 deletions explainers/tesla-flying-roadster/locales/zh.json

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions explainers/tesla-flying-roadster/src/ids.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { describe, expect, it } from 'vitest';
import {
ANCHOR_IDS,
CAMERA_VIEW_IDS,
CAR_ANCHOR_IDS,
CAR_REGION_IDS,
LABEL_PRIORITY,
NOZZLE_IDS,
NOZZLE_PARTS,
PART_IDS,
PHASE_IDS,
PRESET_IDS,
REGION_IDS,
} from './ids';

function unique(values: readonly string[]): boolean {
return new Set(values).size === values.length;
}

describe('ids', () => {
it('names every part, phase, preset, nozzle and view once', () => {
[PART_IDS, PHASE_IDS, PRESET_IDS, CAMERA_VIEW_IDS, NOZZLE_IDS].forEach((ids) =>
expect(unique(ids)).toBe(true),
);
});

it('ranks every part for labels exactly once', () => {
expect([...LABEL_PRIORITY].sort()).toEqual([...PART_IDS].sort());
});

it('gives each chapter its own camera view', () => {
expect(PRESET_IDS).toHaveLength(CAMERA_VIEW_IDS.length);
});

it('has ten nozzles in three part groups', () => {
expect(NOZZLE_IDS).toHaveLength(10);
expect(Object.keys(NOZZLE_PARTS)).toEqual([...NOZZLE_IDS]);
const groups = Object.values(NOZZLE_PARTS);
expect(groups.filter((group) => group === 'cornerNozzles')).toHaveLength(4);
expect(groups.filter((group) => group === 'frontNozzles')).toHaveLength(2);
expect(groups.filter((group) => group === 'rearNozzles')).toHaveLength(4);
});

it('splits regions and anchors into the ones on the car and the ones on the site', () => {
CAR_REGION_IDS.forEach((id) => expect(REGION_IDS).toContain(id));
CAR_ANCHOR_IDS.forEach((id) => expect(ANCHOR_IDS).toContain(id));
});
});
206 changes: 206 additions & 0 deletions explainers/tesla-flying-roadster/src/ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
export const PART_IDS = [
'body',
'wheels',
'seat',
'tank',
'tankValve',
'cornerNozzles',
'frontNozzles',
'rearNozzles',
'jets',
'forceArrows',
'fans',
'runway',
'ramp',
'landingPad',
'ghostPath',
'parkedRow',
] as const;

export type PartId = (typeof PART_IDS)[number];

export const LABEL_PRIORITY: readonly PartId[] = [
'jets',
'cornerNozzles',
'rearNozzles',
'tank',
'ramp',
'landingPad',
'forceArrows',
'ghostPath',
'tankValve',
'seat',
'frontNozzles',
'fans',
'wheels',
'body',
'parkedRow',
'runway',
];

export const PHASE_IDS = [
'sprint',
'settle',
'ramp',
'brake',
'hold',
'deck',
'flip',
'catch',
'runUp',
'leap',
'twist',
'landing',
'rest',
] as const;

export type PhaseId = (typeof PHASE_IDS)[number];

export const MOMENT_IDS = [
'sixty',
'inverted',
'held',
'leavesRamp',
'rollComplete',
'touchdown',
'liftOff',
'apex',
'twistComplete',
'landed',
'stopped',
] as const;

export type MomentId = (typeof MOMENT_IDS)[number];

export const PRESET_IDS = ['tank', 'nozzle', 'boost', 'hook', 'flip', 'jump', 'limits'] as const;

export type PresetId = (typeof PRESET_IDS)[number];

export const CHAPTER_ACTION_IDS = ['gas', 'tank', 'replay', 'moment'] as const;

export type ChapterActionId = (typeof CHAPTER_ACTION_IDS)[number];

export const GAS_IDS = ['air', 'nitrogen', 'helium'] as const;

export type GasId = (typeof GAS_IDS)[number];

export const TANK_CHOICE_IDS = ['400', '600'] as const;

export type TankChoiceId = (typeof TANK_CHOICE_IDS)[number];

export const CORNER_NOZZLE_IDS = [
'cornerFrontLeft',
'cornerFrontRight',
'cornerRearLeft',
'cornerRearRight',
] as const;

export const FRONT_NOZZLE_IDS = ['frontLeft', 'frontRight'] as const;

export const REAR_NOZZLE_IDS = [
'rearOuterLeft',
'rearInnerLeft',
'rearInnerRight',
'rearOuterRight',
] as const;

export const NOZZLE_IDS = [...CORNER_NOZZLE_IDS, ...FRONT_NOZZLE_IDS, ...REAR_NOZZLE_IDS] as const;

export type NozzleId = (typeof NOZZLE_IDS)[number];

export type NozzlePartId = Extract<PartId, 'cornerNozzles' | 'frontNozzles' | 'rearNozzles'>;

export const NOZZLE_PARTS: Readonly<Record<NozzleId, NozzlePartId>> = {
cornerFrontLeft: 'cornerNozzles',
cornerFrontRight: 'cornerNozzles',
cornerRearLeft: 'cornerNozzles',
cornerRearRight: 'cornerNozzles',
frontLeft: 'frontNozzles',
frontRight: 'frontNozzles',
rearOuterLeft: 'rearNozzles',
rearInnerLeft: 'rearNozzles',
rearInnerRight: 'rearNozzles',
rearOuterRight: 'rearNozzles',
};

export type NozzleThrusts = Readonly<Record<NozzleId, number>>;

export const VIEW_IDS = ['nozzles', 'forces', 'tank', 'ghost', 'labels'] as const;

export type ViewId = (typeof VIEW_IDS)[number];

export type ViewOptions = Record<ViewId, boolean>;

export const CAMERA_VIEW_IDS = [
'tankClose',
'nozzleClose',
'boostSide',
'rampWide',
'flipChase',
'jumpWide',
'siteWide',
] as const;

export type CameraView = (typeof CAMERA_VIEW_IDS)[number];

export const REGION_IDS = ['site', 'car', 'cabin', 'tail', 'ramp', 'arc', 'jump'] as const;

export type RegionId = (typeof REGION_IDS)[number];

export const CAR_REGION_IDS = ['car', 'cabin', 'tail'] as const;

export type CarRegionId = (typeof CAR_REGION_IDS)[number];

export type SiteRegionId = Exclude<RegionId, CarRegionId>;

export const ANCHOR_IDS = ['car', 'tank', 'tail', 'rampExit', 'pad', 'row'] as const;

export type AnchorId = (typeof ANCHOR_IDS)[number];

export const CAR_ANCHOR_IDS = ['car', 'tank', 'tail'] as const;

export type CarAnchorId = (typeof CAR_ANCHOR_IDS)[number];

export type SiteAnchorId = Exclude<AnchorId, CarAnchorId>;

export type Point = readonly [x: number, y: number, z: number];

export interface CarReading {
position: Point;
speed: number;
height: number;
roll: number;
pitch: number;
onGround: boolean;
onRamp: boolean;
airborne: boolean;
}

export interface TankReading {
pressureBar: number;
massKg: number;
massShare: number;
empty: boolean;
emptyAtSeconds: number | null;
shortfallMs: number;
}

export interface ChaseTarget {
position: Point;
heading: number;
pitch: number;
roll: number;
span: number;
}

export interface AssemblyState {
phase: number;
playing: boolean;
gas: GasId;
tankLitres: number;
car: CarReading;
nozzles: NozzleThrusts;
tank: TankReading;
fans: boolean;
view: ViewOptions;
}
27 changes: 27 additions & 0 deletions explainers/tesla-flying-roadster/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import './style.css';
import { defineExplainer } from '@core/explainer';
import { SCENE_OPTIONS, mountThrusterCarScene } from './scene';
import { PART_INFO } from './scene/partInfo';
import { PRESETS, createThrusterCarStore } from './state';
import type { ThrusterCarStoreState } from './state';
import { THRUSTER_CAR_TIMELINE } from './timeline';
import {
CHAPTER_ACTIONS,
THRUSTER_CAR_CHOICES,
THRUSTER_CAR_READOUTS,
VIEW_TOGGLES,
mountThrusterCarUi,
} from './ui';

export default defineExplainer<ThrusterCarStoreState>({
timeline: THRUSTER_CAR_TIMELINE,
presets: PRESETS,
parts: PART_INFO,
createStore: () => createThrusterCarStore(),
dock: { choices: THRUSTER_CAR_CHOICES, toggles: VIEW_TOGGLES },
readouts: THRUSTER_CAR_READOUTS,
actions: CHAPTER_ACTIONS,
scene: SCENE_OPTIONS,
mountScene: mountThrusterCarScene,
mountUi: mountThrusterCarUi,
});
37 changes: 37 additions & 0 deletions explainers/tesla-flying-roadster/src/model/boost.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, it } from 'vitest';
import { ENTRY_SPEED_MS, SIXTY_MPH_MS } from './constants';
import {
SETTLE_DECEL,
SETTLE_SECONDS,
SPRINT_ACCEL,
SPRINT_DISTANCE_M,
SPRINT_SECONDS,
SPRINT_THRUST_N,
boostAccel,
boostLastsSeconds,
zeroToSixtySeconds,
} from './boost';
import { RAMP, SITE } from './layout';
import { G } from './constants';

describe('boost', () => {
it('runs 0 to 60 mph in 0.81 s at 3.4 g on about 56 kN', () => {
expect(zeroToSixtySeconds(SPRINT_THRUST_N)).toBeCloseTo(SPRINT_SECONDS, 9);
expect(SPRINT_ACCEL / G).toBeCloseTo(3.4, 1);
expect(SPRINT_THRUST_N / 1000).toBeCloseTo(56.3, 1);
expect(SPRINT_DISTANCE_M).toBeCloseTo(SITE.finishLineX, 1);
});

it('brakes at about 4 m/s2 to reach the loop at 18 m/s', () => {
expect(SETTLE_DECEL).toBeCloseTo(4.0, 1);
expect(SPRINT_SECONDS + SETTLE_SECONDS).toBeCloseTo(3.0, 2);
const settled = SIXTY_MPH_MS * SETTLE_SECONDS - 0.5 * SETTLE_DECEL * SETTLE_SECONDS ** 2;
expect(SPRINT_DISTANCE_M + settled).toBeCloseTo(RAMP.entryX, 9);
expect(SIXTY_MPH_MS - SETTLE_DECEL * SETTLE_SECONDS).toBeCloseTo(ENTRY_SPEED_MS, 9);
});

it('empties 600 L of air in 2.7 s at the full 80 kN', () => {
expect(Math.floor(boostLastsSeconds(80000, 'air', 600) * 10) / 10).toBe(2.7);
expect(boostAccel(17000)).toBe(10);
});
});
24 changes: 24 additions & 0 deletions explainers/tesla-flying-roadster/src/model/boost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { GasId } from '../ids';
import { CAR_MASS_KG, ENTRY_SPEED_MS, SIXTY_MPH_MS } from './constants';
import { massFlow, storedMass } from './gas';
import { RAMP } from './layout';

export const SPRINT_SECONDS = 0.81;
export const SPRINT_ACCEL = SIXTY_MPH_MS / SPRINT_SECONDS;
export const SPRINT_THRUST_N = CAR_MASS_KG * SPRINT_ACCEL;
export const SPRINT_DISTANCE_M = (SIXTY_MPH_MS * SPRINT_SECONDS) / 2;
export const SETTLE_DECEL =
(SIXTY_MPH_MS ** 2 - ENTRY_SPEED_MS ** 2) / (2 * (RAMP.entryX - SPRINT_DISTANCE_M));
export const SETTLE_SECONDS = (SIXTY_MPH_MS - ENTRY_SPEED_MS) / SETTLE_DECEL;

export function boostAccel(thrustN: number): number {
return thrustN / CAR_MASS_KG;
}

export function zeroToSixtySeconds(thrustN: number): number {
return SIXTY_MPH_MS / boostAccel(thrustN);
}

export function boostLastsSeconds(thrustN: number, gas: GasId, litres: number): number {
return storedMass(gas, litres) / massFlow(thrustN, gas);
}
Loading
Loading