Releases: Dkijas/simutrans-blender-kit
Release list
v0.10.0 — from an idea to a train the game will build
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
0.7 could take a finished model and produce a .pak. It could not help you start one, and it could not help you build the second, the fifth, or the train they run in. This release is those three things — 0.8.0, 0.9.0 and 0.10.0 land together.
The research that shaped it
can_follow (vehicle_desc.h:219) decides all coupling in Simutrans, and everything falls out of one line: prev_veh == NULL means "nothing in front of me" — the head of the train.
| Written | can_follow(NULL) — at the head |
can_follow(X) — behind X |
|---|---|---|
| (nothing) | true | true |
none |
true (NULL == NULL) | false |
any |
false (the prev_veh != NULL guard) |
true |
So any is how you say "middle only" — the reverse of how the word reads. vehicle_writer.cc:271 makes none a NULL child; pakset_manager.cc:238 makes any a real sentinel, and without that reassignment none would behave as any — the exact inversion of its meaning. The full table is in docs/constraints-in-simutrans.md.
This kit had that wrong in two places, and they contradicted each other. The panel told you Prev: none made a vehicle "run alone"; it means only at the head. datgen called any "anything at all", omitting that it forbids the head. The wrong one was the tooltip the artist reads.
What you get
Start a scene. Create Template builds the collections, the names, the orientation and the scale that every module in core/ already read but nothing ever wrote. The artist stopped typing internal names out of three lines of prose. Guides are Empties, never meshes — so they cannot land in the render or move the bounds.
Describe the train; the rules fall out. The Consist Manager takes the formation you intend — head-only, middle-only, optional cars, repeatable sections, reversible sets — and derives the constraint_prev / constraint_next pairs. The union is the point: a vehicle in two formations must accept every neighbour either gives it. Miss one and the game does not complain. It just refuses to let you build that train in the depot, and never says why.
Show Constraints prints a diff and writes nothing. A tool that silently rewrites the coupling of every vehicle in a project is one nobody should press.
Two liveries are two objects. There are no liveries in Simutrans — not "unsupported here"; the concept does not exist in the engine (grep -rlnw livery src/ finds nothing). Variants say so, and make the two objects.
See all eight headings. Not a preview of the render — it is the render, of eight headings, assembled onto one page afterwards from the finished frames. Same camera, same sun, because it calls the same two functions the final render is made of. Laid out in the engine's own order (s w sw se n e ne nw), so the page is labelled rather than reordered.
A real component catalogue — wheelset, two-axle bogie, pantograph, headlight, coupler. Built, not committed as art: tools/build_components.py defines each from primitives, so the provenance is the source and the licence is unambiguous. Real numbers: 0.92 m wheels, 1.435 m gauge, UIC coupler height. No third-party art.
Package it. A zip with the .dats, the sprites, your licence, and a manifest naming every file, its checksum, and whether it is generated or authored. It refuses to build if something is missing, and it uploads nothing. It makes a file. Sending it is your decision.
Also in this release: the example assets
Three commits had landed on main and never got a version. The 9000's payload split was wrong and every check passed — the spec guarded the totals, nothing guarded the arithmetic under them, and six literals in the module summed to 186 against the unit's own measured 178. A whole car's worth of seats. The splits moved into spec.json; car_totals names the fact each column must sum to, and a spec whose columns do not add up does not load. A number you cannot type twice cannot drift.
And the Metro 7000 source is here and licensed CC BY 4.0 — it had been on the forum for a day while its source lived on one disk with no licence, so nobody could rebuild the .pak they had downloaded.
Grounded and verified
Every coupling rule is derived from the engine's own can_follow, not from convention. The model reproduces the shipped Civia constraint for constraint. All eight preview headings are asserted byte-identical to the final render. The v1 → v2 document migration is tested against a literal phase-2 document, not one this code produced; a document from the future is handed back untouched rather than guessed at.
Each main function was deliberately broken in an isolated copy to prove its tests fail: reuse a dead key → 4 and 7 failures; accept an impossible formation → 1; lose a neighbour past an optional car → 2; write none where middle only needs any → 4; lose repeatable self-coupling → 3; invent a total → 1; accept a future document → 4.
45 suites green: core, the Blender pipeline, makeobj compilation, and headless loads in the real pak128.
Not measured, honestly:
- Blender 4.x is untested. Everything here ran on Blender 5.1.2, the only version on the build machine.
bl_infodeclares 4.2 as the minimum and the README says "4.x/5.x" — that is a declared target, not an executed one. - Linux and macOS are untested. The build machine is Windows. Paths go through
os.path, which is an argument, not a result. - One break produced no failures and the test was not weak: changing the preview camera's distance is a no-op for an orthographic camera, exactly as documented.
- The Civia's per-car splits remain unguarded — it declares no
formation, so only the field checks apply to it.
v0.7.0 — pakset profiles measured against the real pakset
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
Pakset profiles measured against the real pakset, not transcribed and hoped — and the one measurement that changes the artist's advice put in front of them.
The numbers now come from the pakset, not from us
core/paksets.py carries numbers that belong to the pakset — tile_height, and now height_conversion_factor — read from its config/simuconf.tab. The old test proved those numbers were self-consistent; consistency is not correctness, and it cannot catch a transcription that is self-consistent and simply wrong, nor a pakset that changed the value in a new release. The module's own docstring named the failure: "the sort of wrong number that waits" until something reads the field.
So a new profile test suite reads the real simuconf.tab of every mounted pakset — the demo pak in the engine source, the pak128 testbed — with the engine's own parsing (# a comment only at column 0, values read strtol-style) and asserts our profile equals it. It skips when nothing is mounted, so CI stays green while a local run verifies against the artefact. tools/measure_pakset.py is the instrument.
The fact it surfaced: pak128's hills are double by default
pak128 declares height_conversion_factor = 2. With factor 2 the engine turns a terrain step into a double-height slope (slope_from_slope4, grund.cc / tunnelboden.cc / brueckenboden.cc), so pak128's ordinary hill is double. That makes the double-slope way image (imageup2, the way_slope2 model) the common case for pak128 — not the optional decoration it is for a single-height pakset.
So the panel now says so: render a way for pak128 with a single ramp but no way_slope2, and it warns that the single-height slope image will be stretched over every ordinary hill (way_desc.h:176), and to model way_slope2. It stays silent for the demo pak, where the double slope is genuinely rare, and once the double ramp is modelled — no crying wolf.
Grounded and verified
Every number is read from the real pakset with the engine's own parsing, and the double-slope rule is read off way_desc.h, grund.cc, settings.cc and environment.cc. The profile suite was proved adversarially — forge a wrong profile (the historical tile_height = 16, twice too steep) and it fails and names the drift; it is not a tautology. The panel warning is tested both ways: the pure decision without Blender, and a panel test that models a pak128 way with way_slope but no way_slope2 and asserts the warning reaches the panel, then adds way_slope2 and asserts it goes quiet.
No sprite the kit produces changed. This is reference data made trustworthy, and one measured fact turned into advice the artist actually sees.
Not measured, honestly: pak192 and pak256 are not mounted here, so they carry the engine default; the profile suite will measure them the day they are.
v0.6.0 - tunnels, stations, bridges, factories
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
The release that finished the object types: everything a pakset is built from can now come out of the kit.
Added — four new object types
-
Tunnels. A portal in four directions and two layers (back behind the vehicle, front over it), modelled on a north-facing ramp in
tunnel_portal/tunnel_portal_front. Grounded intunnel_writer.ccand the engine'sslope_indices; the mandatory icon is emitted (without it the engine gives the tunnel no builder). Verified buildable in the running game. -
Stations and depots. A stop, a depot or an extension is a building with a
type, awaytypeand — because a player places it — an icon, without whichhausbauer.ccgives it no builder and it silently cannot be built. Pick the kind and set what it accepts (passengers, mail, goods). Verified: the stop turns up in the engine's own station builder list. -
Bridges. The four image groups the engine reads — span (ns/ew), start and ramp (n/s/e/w), pillar (s/w) — each in a back and an optional front layer, plus the length, height and pillar-spacing limits. Modelled in
bridge_span,bridge_start,bridge_ramp,bridge_pillar. Verified buildable in game. -
Factories. A building plus its economics in one
obj=factory: the mandatory minimap colour, productivity, location, and the goods it makes or consumes (each a cross-reference that must resolve at load). The sprites are the ordinary building render. Verified: the factory loads with its good resolved and appears in the engine's factory table.
Every one is grounded in the engine's own writers, checked against makeobj (which fatals on a missing freightimagetype, mapcolor or icon), rendered and pixel-checked in Blender, and loaded in the running engine.
One caveat, held honestly: the exact portal/piece-on-which-hill mapping for tunnels and bridges is derived from the engine's slope tables but is the reflection the way slopes proved you confirm by eye in a windowed game, not headless — the pieces are verified buildable; their per-slope orientation is the one thing a headless run cannot see.
Everything from v0.5.0 (cargo variants) and v0.4.0 (render progress + cancel, CI) is included. The complete test suite — 35 suites, from pixel checks to the running pak128 game — is green.
v0.5.0 - cargo variants
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
Added
-
Cargo variants (freight images). A wagon can now show a different sprite for each good it carries — empty, loaded with coal, loaded with oil. Put each load in a collection
freight_0,freight_1, … (the same additive convention as seasons), list the goods in the new Cargo variants field (Kohle, Oel), and the render produces the empty sheet plus one loaded sheet per good and writes theemptyimage/freightimage[i]/freightimagetype[i].datthe engine needs.Grounded in the engine source throughout: the engine requires exactly one
freightimagetypeper freight image and makeobj FATALs without it; it shows the empty image when the wagon is unloaded and the matching freight image when it is not, falling back to index 0. There are no "livery" images in base Simutrans — that is a Simutrans Extended feature — so this is freight, precisely.Verified end to end: the emitted
.datcompiles under makeobj (and fails without thefreightimagetypelines); the empty and loaded sprites differ pixel for pixel in every heading; and the wagon loads in the running engine, is buyable, and its freight goods really resolve — which only the game can prove, because makeobj defers the goods to load time.
Everything from v0.4.0 (render progress + cancel, CI) is included.
v0.4.0 - render progress and cancel
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
Added
- Render Sheet shows progress and can be cancelled. A vehicle render fires the renderer eight times with the UI frozen and no way out. It now renders one heading per tick with a progress bar and a status-bar readout (
heading N/8), and Esc stops it. Because the render call is itself blocking, the gain is an update-and-cancel point between headings — which is where the seconds actually go. A cancelled render is left on disk but not recorded, so Write .dat still builds only from the last complete render, never half a sheet.
Changed
- CI runs the core suite on every push (GitHub Actions, Python 3.10–3.12): the ~970 Blender-free checks — projection, colours, the
.datlinter, schema drift. doc_urland the README now point at the GitHub repository and its releases.
Everything from v0.3.0 is included. The progress/cancel path was verified by hand in Blender 5.1 (headless has no event loop, so the modal operator cannot be covered by the automated suite); execute(), the shared reporter, and the new strings are covered by the green core and panel suites.
v0.3.0 — the release that made the kit's claims true
The release that made the kit's own claims true and closed the gaps that let broken art ship green.
Install: download simutrans_blender_kit.zip below, then in Blender go to Edit → Preferences → Add-ons → Install…, pick the zip, and enable "Simutrans Blender Kit".
Fixed — things that produced wrong artefacts
- Signals never lit. The purple signal lamp was written as
#E100E1, the colour the game draws, not#FF017F, the colour makeobj matches. A signal painted the way the kit recommended compiled its lamp as a flat colour that could never light. The reserved-colour table is now read from the engine source, not transcribed, and every one of the 31 colours is checked byte-for-byte inside a real.pak. - Ways and catenary were invisible on slopes. Neither emitted slope images, and the engine has no fallback for them (
weg.cc:545,wayobj.cc:270are unguarded), so anything the kit made vanished on every hill. The artist now models one more shape (way_slope/wayobj_slope) and the four slope images are emitted — all four or none, because the engine indexes them by position and a partial set draws the wrong image. - The pakset height step was wrong. It was hard-coded to 16 for every pakset; pak128's own
simuconf.tabsays 8. It had never bitten because the field was read by nobody — until the slopes needed it. Now taken per pakset. - Sprites carried the author's home directory. Blender writes the
.blend's absolute path into every PNG's metadata; every published sprite leaked it. Turned off, and pinned by a byte-scan test. - The render engine and colour management were unpinned. The same
.blendgave different sprites under EEVEE and Cycles, and the tone-mapping-off setting was wrapped in a silenttry/except. Both are now set and verified; the kit refuses to render rather than produce silently wrong colour.
Added
- Material buttons. Player colour, the night lights and plain paint now have a panel button. They were reachable only from a script before — the one thing an artist most needs, and the hardest to get to.
- Render warnings reach the panel. Clipping and accidental-reserved-colour warnings used to print to a console no artist has open while the panel said "Rendered". They are reported in the panel now.
- Write .dat without re-rendering. Change a number — power, cost, a coupling — and rewrite the
.datfrom the last render's frames instead of rendering every heading again. - Linter: value validation, rule codes,
--json, and an ignore pragma. It checks that a number key is given a number, tags each finding with a stable code, can emit JSON, and honours# bkit: ignore=<code>. Two silent bugs fixed: it crashed on a malformed range, and its missing-icon rule was masked for a second object of the same type in one file.
Changed
- The test loop is closed. Nothing used to compile the
.pakthe game suites loaded; they ran against art that could be days old. The runner now renders, compiles and installs from this run's sources, and the game scenarios assert what they report (car count, order, that the unit is really electric, that it moved) instead of printing it. - Licence is MIT (see
LICENSING.md), replacing an Artistic 1.0 claim that was GPL-incompatible for abpyadd-on. - The example trains credit their modeller (
victor_18993), not the tool. - The toolchain (makeobj, Blender, the headless engine) is discovered rather than assumed to sit at Windows paths, so the suite runs on Linux and macOS too.