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
2 changes: 1 addition & 1 deletion .claude/skills/screenshot/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Bild (KI-Züge sind gefahren, Luftbildkacheln sind geladen). 300 Frames für gel
|---|---|
| `--hud <stufe>` | `full`, `reduced` oder `off` — die drei Stufen der Anzeige (F7). Schreibt die Einstellungsdatei nicht. |
| `--overlays` | Öffnet Tastenhilfe (F5) und Diagnose (F6) von Anfang an — sonst nur per Tastendruck erreichbar. |
| `--menu [seite]` | Fotografiert das Hauptmenü statt der Welt dahinter. Seite optional: `root` (Vorgabe), `line`, `loco`, `scenario`, `mods`, `settings`, `controls`. |
| `--menu [seite]` | Fotografiert das Hauptmenü statt der Welt dahinter. Seite optional: `root` (Vorgabe), `run` (die Fahrtauswahl, früher `scenario`), `line`, `loco`, `setup`, `mods`, `settings`, `controls`. |
| `--camera <modus>` | `outside` für die Außenkamera (Fahrzeugmodelle), `walk` für zu Fuß, `fly` für die Freikamera des Konsolenbefehls `fly` — alles sonst nur per Taste oder Konsole erreichbar. |
| `--fly R,H,V` | Nur mit `--camera fly`: wo die Freikamera steht, in Metern **rechts**, **über** und **vor** dem Zug. Vorgabe `25,6,0`. |
| `--look R,H,V` | Wohin sie schaut, im selben Bezug. Vorgabe `0,2,0` — der Zug. Ohne die beiden kann eine Aufnahme nur den Zug zeigen, und alles über etwa zehn Metern läuft oben aus dem Bild. |
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,25 @@ cargo run -p app -- --connect 127.0.0.1:27015 # join one
```

Without arguments the simulator opens on a title screen: wordmark over the backdrop, and four
verbs — **Drive**, **Mods**, **Settings**, **Quit**. Drive walks line → vehicle → run in
three steps, shown as a numbered rail across the top with what was picked under each. Beside
the list a detail pane reads the highlighted entry out of the loaded content: length, permitted
speed and signals of a line; mass, running-gear limit, drive and brake of a vehicle; start time,
timetable and events of a scenario. The third step offers **two kinds of run**: a scenario, and
under a heading of its own every playable **service of an operating day** — a whole 24-hour
timetable that starts over at midnight (see *Timetable runs* below). Picking a service opens
one more page, where the **date** and the **weather** are set before the run starts.
verbs — **Drive**, **Mods**, **Settings**, **Quit**. Drive begins with **the run**, and
everything else follows from it. The list offers **two kinds of run**: a scenario, and every
playable **service of an operating day** — a whole 24-hour timetable that starts over at
midnight (see *Timetable runs* below). Both stand under the **route** they take, and picking
one picks that route with them: a scenario names the line it plays on, a service the line of
its plan, so the route is derived rather than asked for. Only what the run leaves open is a
step of its own — the line where the run names none (which is what the free run is), the
vehicle where the run does not bring one, and for a service the **date** and the **weather**
before it starts. The steps stand in a numbered rail across the top with what was picked under
each, so a prepared scenario is one question and the free run is three. Beside the list a
detail pane reads the highlighted entry out of the loaded content: length, permitted speed and
signals of a line; mass, running-gear limit, drive and brake of a vehicle; start time,
timetable and events of a scenario; train number, departure and arrival of a service.
`↑`/`↓` or the mouse select, `Enter` or a left click
confirms, `←`/`→` dial a setting, `Esc` goes one step back and leaves at the title screen; `F9`
opens the mod manager in-game. Any run flag (`--line`, `--loco`, `--scenario`, `--day`,
`--frames`, `--screenshot`, …) skips the menu entirely, so the invocations above stay
non-interactive — `--menu` puts it back in front, optionally on a named page
(`--menu settings`, also `root`, `line`, `loco`, `scenario`, `run`, `mods`), which is the only
(`--menu settings`, also `root`, `run`, `line`, `loco`, `setup`, `mods`), which is the only
way to photograph the menu itself.

The picture behind the menu lives in `crates/app/images/` and is compiled into the binary. The
Expand Down
32 changes: 20 additions & 12 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ As of 2026-08-31 · `cargo test --workspace`: **1136 tests green** · clippy and
| **M5** | LZB 80 + AFB, MFA, tap-changer loco | **done** — LZB with guidance, braking curve, end and failure procedures, with and without PZB, full/partial block mode and CIR-ELKE; BR 110 present; **AFB** as vehicle equipment (`VehicleSpec::afb`): holds the dial speed with traction, dynamic brake and — where that does not suffice — the air brake, and under LZB guidance runs down the braking curve because the LZB's v-soll caps the dial; MFA values and lamps ship as indicators — HUD text, `gauge:`/`lamp:` instruments and render-to-texture displays in the 3D cab (see M6) |
| **M6** | Interactive 3D cab, start-up procedure, audio, weather/night | **done** — interactive 3D cab: per-vehicle cab data (eye point + controls binding glTF nodes to a closed input registry incl. wipers, lights and display softkeys), mouse picking with drag/click/scroll gestures per control kind, hover glow, HUD readout, operating clicks via `Control(…)` sound quantities; instruments: gauges/lamps of the safety systems (`gauge:`/`lamp:` indicators, MFA pointers), `digit:` seven-segment counters, and **displays rendered to texture** (declarative widget lists in RON, a Lua `display(ctx)` hook with nested menus and clickable softkeys, or an HTML/CSS/JS page per screen — parsed, flex-laid-out and scripted in-engine by the `html-display` crate, no browser embedded); edited in the vehicle editor with viewport preview; start-up chain operable via keyboard and mouse; **weather** (plan 14.1): `sim_core::weather` holds it as physical quantities — cover and cloud base, precipitation kind and rate [mm/h], wind speed and bearing, sight, temperature and a thunder rate — moved between thirteen named presets over a five-minute transition by the `SetWeather` scenario action, with the surface water and the lying snow integrated in the fixed step and the rail condition falling out of them (the first rain on a dry rail is greasy before it is merely wet). Nothing of it is replicated: between two scenario actions the weather is a pure function of the scenario clock, lightning included, so every client stands in the same rain and sees the same flash. Rendered as **clouds in two tiers, switched by a graphics setting** (`world_render::clouds`): both write a 2048 × 1024 equirectangular panorama through an offscreen camera and show it on a dome in the transparent phase, filtered cubically — a camera on the ground never enters a cloud, so a direction is all a cloud has to be a function of. The panorama is **amortised over sixteen frames** on a 4 × 4 Bayer slot, which is what pays for 0.18° a texel at fewer texels a frame than the 768 × 384 panorama it replaces, and **accumulated over about a second** — two buffers swapping roles each frame, every march blended into its texel, with the ray sent through a new point of the texel, started a new way into its first step and aimed along a new line of the light cone each turn, so the blend converges on a filtered edge and a noise-free body instead of freezing one sample's raster into the sky; the history is read where the deck has drifted from over the turn, so a moving cloud is followed rather than smeared. Volumetric is a Nubis-style raymarch (gradient-Perlin-Worley shape at 128³ carved by a Worley detail volume — wisps at the base, billows above — sampled anisotropically so a deck billows upwards instead of extruding one horizontal slice, Beer attenuation with a powder term for front-lit views only, a dual-lobe phase for the silver lining, 96 steps along the ray and four multiple-scattering octaves, an ambient read from Bevy's own atmosphere cubemap with the sun diffused two-stream through the body of the cloud so a closed deck is a grey sky rather than a black slab, and an analytic aerial perspective that fades a far cloud into that same sky); the fallback reads the same field on three slices and walks the self-shadow across that height field, a dozen fetches against several hundred, so a weak machine loses the billows rather than the sharpness. The deck both **drifts** (at 2.5 × the reported ten-metre wind, which is roughly what blows above the friction of the ground) and **evolves** — the march walks the unused part of the shape volume's vertical axis over time, so clouds grow and dissolve instead of only sliding past, and the cover breathes with it; both are functions of the scenario clock and cost nothing over the network, **haze in the atmosphere itself** (a Koschmieder extinction as an extra `ScatteringMedium` term, so fog is blue at dusk and bright around the sun, plus an analytic near-field falloff below 8 km of sight, which the planetary look-up tables cannot resolve), **wet and snowed-on surfaces** (`weather.wgsl`, shared by the terrain and by an extension swapped over every mod material as it spawns: Lagarde's albedo darkening and roughness, procedural ripple normals where the drops land, snow by world normal with a ragged edge, and the dapple of the clouds on the ground), **rain and snow around the camera** (one draw call, thinned by the intensity in the shader, added rather than blended, leaning into the wind of the weather plus the train's own rush of air, and off in a tunnel because the track type says where one is), **lightning and thunder** (a strike read off the clock, lighting the cloud deck and the ground, with a `Thunder` sound quantity delayed by `distance / 343 m/s` and rolling longer the further it struck) **rain on the cab glass** (`world_render::windscreen`: the panes a vehicle names in its `cab:` block get their own material — a film that thickens with the weather, drops in a cell grid that crawl down the glass at a stand and are pushed up it by the airflow above about 15 km/h, and the strip the wiper leaves clear, sampled from the same sweep curve the blade is drawn with) and **ground mist** as a Bevy fog volume with the sun's shafts through it (a graphics setting of its own). `--weather <preset>` places one for a screenshot; `mods/example/scenarios/regenfahrt.ron` shows a run into rain and fog; terrain from the DGM; **day/night cycle with a physically based sky** (`world_render::sky`): Bevy's implementation of Hillaire's scalable sky-and-atmosphere technique (transmittance, multiple-scattering, sky-view and aerial-perspective LUTs — Rayleigh and Mie scattering, so the blue noon, the red sunset and the haze over a distant valley all fall out of one model), the sun's disk drawn into it by the atmosphere itself, a moon disk half a degree wide shaded from the real sun direction (phase, terminator and earthshine out of the almanac), and the 8 900 naked-eye stars of the HYG catalogue as point sprites in J2000 equatorial coordinates plus a procedural Milky Way — turned into the local sky by the observer's latitude and the sidereal time, and extincted by air mass near the horizon; the scenario's start clock (date + time) and the georeferenced location are the whole input; **seasons** (ch. 14 "seasons v2"): the same start date colours ground textures and placeholder vegetation — meadows turn through October, ground, gravel and foliage go under snow from November to March — and a mod may add optional `autumn_model`/`winter_model` variants to its track objects, falling back to the year-round model where it ships none; **night lighting**: signal lamps glow (HDR + bloom on the main camera, emissive lenses), headlight cones at both train ends follow the light switch, the direction of travel and the darkness, red tail lamps (Zg 101) mark the opposite end, **mods' `_NIGHT` nodes** (lit windows, glowing signs) switch at dusk in every model, cab light on its own switch (`CabControl::Headlights`/`CabLight`, keys 9/0) and **instrument backlighting on its own dimmer** (`CabControl::InstrumentLight`, keys `,`/`.`) — a part on the new `Motion::Emissive`, which scales the emissive colour of the mod's own material by the dimmer instead of switching the node, so the dials come up out of the dark continuously (content per vehicle; the example BR 101 carries a backlit panel); **terrain texturing and vegetation** (ch. 14): texture splatting — per-vertex weights from slope and track distance blend three generated ground textures (grass/rock/gravel) in a `StandardMaterial` extension — and vegetation as **line content**: every tree its own `LineSource::trees` entry (3D objects from mods' `objects/*.ron`, placeholder for the unnamed), spawned as children of their terrain tile so they stream with it and batch into instanced draws; woods are baked into single trees by the editor, so each one stays individually editable; no recorded samples (the sources are generated — content, not code) |
| **M7** | Pilot line from OSM/DGM, scenarios, scoring, save/load | **largely done** — scenario system, scoring, save/load and the OSM/DGM importer are in place; only a real pilot line is missing (data procurement) |
| **M8** | Mod runtime: declarative content plus Lua behaviour | **done** — loader with dependency order, vehicles/lines/compositions/scenarios/timetables/signal types/signal models/track types/track objects as RON, signal state machine as data, four Lua hooks (vehicle, signal aspect, line, scenario) with a sandbox, main menu with line, vehicle and scenario selection from the loaded mods (keyboard and mouse), mod manager on the same menu (a toggle applies on start) and under F9; reference mod under `mods/example` incl. a glTF model. Only distribution (`.crails` zip + installer) is still open |
| **M8** | Mod runtime: declarative content plus Lua behaviour | **done** — loader with dependency order, vehicles/lines/compositions/scenarios/timetables/signal types/signal models/track types/track objects as RON, signal state machine as data, four Lua hooks (vehicle, signal aspect, line, scenario) with a sandbox, main menu with run, line and vehicle selection from the loaded mods (keyboard and mouse), mod manager on the same menu (a toggle applies on start) and under F9; reference mod under `mods/example` incl. a glTF model. Only distribution (`.crails` zip + installer) is still open |

## What is in place

Expand Down Expand Up @@ -307,8 +307,8 @@ As of 2026-08-31 · `cargo test --workspace`: **1136 tests green** · clippy and
- **Operating days (ch. 11):** the second way a line is driven, beside the scenarios. An
`OperatingDay` (`sim_core::day`, `days/*.ron` in a mod, plus a built-in one for the
Musterbahn) is a whole day of **services** with wall-clock times that loop every 24 hours;
the run picker lists every playable one under a heading of its own, and the run starts two
minutes before the service departs. The rest of the plan runs around it: a service claims a
the run picker lists every playable one under a heading naming its route, and the run starts
two minutes before the service departs. The rest of the plan runs around it: a service claims a
train `LEAD` before it leaves and gives it back `TAIL` after its last arrival, and between
two workings the unit is **stabled** — not driven, not drawn, and skipped by the occupancy
detection, so it is genuinely off the line. The next service that needs the same stock takes
Expand Down Expand Up @@ -1663,10 +1663,15 @@ As of 2026-08-31 · `cargo test --workspace`: **1136 tests green** · clippy and
scoring with it; without one only the scenario points count.
Lines and scenarios have their own hooks (`on_load`, `on_frame`): the script decides *when*
an event fires, the actions of that event stay declarative RON — an event with
`trigger: Never` waits for the script. **The main menu picks line, vehicle and scenario**
from the loaded mods — three list pages, each opening with the built-in default, so a
run starts even with nothing installed; lines and compositions share one list, since
`resolve_line` takes either name. **The mod manager lives on the same menu** (installed
`trigger: Never` waits for the script. **The main menu picks the run first, and the route
follows from it** — a scenario names the line it plays on, a service the line of its
operating day, so a run is offered under that route and never on another one (`Route` in
`crates/app/src/menu.rs`; `world::build` lets the run's own line beat the menu's, with only
`--line` above it). What the run leaves open is still a page: the line where it names none,
the vehicle where it brings no consists and no `vehicle:`, and for a service the date and
the weather. Each list opens with the built-in default, so a run starts even with nothing
installed; lines and compositions share one list, since `resolve_line` takes either name.
A run whose route no installed mod brought is not offered at all. **The mod manager lives on the same menu** (installed
mods with version, on/off state, missing dependencies and the loading warnings);
switching writes `enabled` back into `mod.ron` (that one field only) and takes effect
when the run starts, because the world is built only on leaving the menu. F9 opens the
Expand Down Expand Up @@ -2147,11 +2152,14 @@ Every simplification is marked with a `ponytail:` comment at the code site, with
genuinely has to see every step moves into `Sim::step`.
- **The main menu is built from plain Bevy UI nodes** — no `egui` in the simulator. A
**title screen** (wordmark over the backdrop and four verbs set large) and behind it a
**full-width flow**: the three steps of picking a run stand in a numbered rail across the
top with what was picked under each, the list sits left, and a detail pane on the right
shows what the highlighted row actually is — length, permitted speed and signal count of
a line, mass, running-gear limit, drive and brake of a vehicle, start time, timetable and
event count of a scenario, all read off the same data the simulation runs on. There is
**full-width flow**: the steps of picking a run stand in a numbered rail across the top
with what was picked under each — as many as the run leaves open, so a scenario that
names its line and brings its own consists is one step and the free run is three. The
list sits left, and a detail pane on the right shows what the highlighted row actually
is — length, permitted speed and signal count of a line, mass, running-gear limit, drive
and brake of a vehicle, start time, timetable and event count of a scenario, train
number, departure and arrival of a service, all read off the same data the simulation
runs on. There is
deliberately **no navigation rail down the side**: a rail plus a content pane is the shape
of a web dashboard and reads as one whatever it is coloured; the step rail is the
breadcrumb, and Esc is the way home. Keyboard and mouse drive the same selection index:
Expand Down
Loading