Create / Mod files / Ship skin styles for mods
Ship skin styles for mods
A Style is the LOOK a ship's derived cladding wears: what its plates are made
of, and the decoration scattered over them. A ship names one by id
(style: Some("raider") on its hull) and wears it.
Styles are content like sections and scenarios. Create a new id to add a look,
or reuse a base id to restyle every ship that already names it. A style ships
its own decoration models the same way a section ships its render mesh - as
.glb files in your bundle, referenced with self://.
This page is the field-by-field style reference. For general RON spelling rules
such as double parentheses, Some(...), and asset schemes, see
RON spelling rules.
What a style can and cannot change
A ship's SKIN is derived from the structure it wraps - see Cladding. The SHAPE of every plate is a function of the hull, so a style cannot author one. What it authors is:
- the MATERIAL of each plate surface, and
- the DECORATION bolted on top - vents, ribbing, blisters, masts.
Decoration is destructible in the full sense: each piece carries its own health, mass and collider, stops rounds, and comes off when it is shot out, leaving the plate behind it bare. Nothing decorative does anything else - if losing a thing should cost the ship an ability, that thing is a section, not decoration.
The Style item
[
Style((
id: "my_raider_look",
name: "Raider",
surfaces: [
(surface: Top, color: Srgba((red: 0.4, green: 0.3, blue: 0.25, alpha: 1.0)), roughness: 0.8, metallic: 0.1),
(surface: Wall, color: Srgba((red: 0.2, green: 0.15, blue: 0.12, alpha: 1.0)), roughness: 0.9, metallic: 0.1),
],
fixtures: [
(
id: "antenna",
model: "self://gltf/greebles/antenna.glb#Scene0",
health: 8.0,
density: 0.05,
collider: (0.12, 0.38, 0.12),
scatter: (
relief: [Ridge, Peak, Spur],
facing: Up,
min_depth: 2,
chance: 0.3,
align: Outward,
),
),
],
)),
]
| field | type | default | meaning |
|---|---|---|---|
id |
string | required | what a ship names this style by. A base id REPLACES that look |
name |
string | required | display name |
surfaces |
list | [] |
one entry per plate surface to dress. A surface left out keeps the built-in colour |
fixtures |
list | [] |
the decoration, in PRIORITY order - see Priority |
surfaces
| field | type | meaning |
|---|---|---|
surface |
Top / Wall / Floor |
which face of a plate. Top faces space; Wall is the side a plate drops away at; Floor is against the hull and never seen |
color |
color | base colour, tagged: Srgba((red: .., green: .., blue: .., alpha: ..)) or LinearRgba((..)) - both spellings of the engine colour type parse; the shipped kits author LinearRgba |
roughness |
float | 0 (mirror) to 1 (matte) |
metallic |
float | 0 (dielectric) to 1 (metal) |
Top is nearly the whole of what a camera sees. Measured, by painting
Wall a colour nothing else uses and shooting the wfc_ships row: two plates
in a run press their walls together and neither is ever seen, so Wall comes
back only at the skin's OUTER RIM and on the side of a plate climbing past a
lower neighbour - roughly a twentieth of the hull. A dark wall under a pale top
therefore does NOT draw a panel line at every plate boundary; it makes the
silhouette read thick, like plate with depth. Panel lines are geometry, or they
are the livery. Budget your effort accordingly.
fixtures
| field | type | default | meaning |
|---|---|---|---|
id |
string | required | names the piece within its style, and SALTS its scatter - two pieces sharing one rule do not claim the same plates |
model |
asset ref | required | the .glb scene, schemed. See The frame a greeble is authored in |
health |
float | required | what the piece takes before it comes off |
density |
float | required | mass per unit of collider volume. A greeble is light - the base placeholders run 0.05 to 0.2, against a plate's 0.25 |
collider |
(x, y, z) |
required | the box a round stops on, in cells, standing on the mounting face. Not the model: a hull of one would cost more than it is worth |
scatter |
rule | every plate | where the piece may stand |
The scatter rule
Every field is a filter over the plate's NEIGHBOURHOOD, except the last three. An empty rule matches every plate.
| field | type | default | meaning |
|---|---|---|---|
seat |
Whole/Any |
Whole |
what the plate's TOP must be: one unbroken surface, or anything at all - see The seat |
relief |
list | any | which ZONE of the hull the piece may stand in - see The seven reliefs |
facing |
Any/Up/Down/Side |
Any |
which way the plate faces in the SHIP's own frame |
min_run |
int | 0 | the shortest run of LIKE plate the piece will stand on |
min_height |
int | 0 | how much of its cell the plate must fill, in quarter cells (0-4) |
min_border |
int | 0 | how far in from the end of that run the plate must be |
max_border |
Some(int) |
none | how far in it may be at most. Some(0) is TRIM: only ever at the end of a run |
min_depth |
int | 0 | how many cells of ship must stand under the plate |
min_enclosure |
int | 0 | how many of the eight surrounding cells the surface carries on into (0-8) |
near_fitting |
Some(int) |
none | how many steps across the surface the nearest fitting - a drive, a bay, a gun mount - may be at most |
stride |
int | 1 | the LATTICE the piece claims cells on. 2 is every other cell on both in-plane axes |
chance |
float | 1.0 | the share of the plates that pass everything above which take the piece |
patch |
int | 0 | at least one piece per block of this many cells - see Density |
align |
Free/Run/Outward |
Free |
which way the piece is turned - see Alignment |
The seat: one surface, or a crease
A plate's top is either ONE PLANE or a CONE, and nothing in between. seat is
the filter for it, and it is the one filter that is ON by default:
seat |
what it takes |
|---|---|
Whole |
plates whose top is one unbroken surface. TILTED COUNTS - a ramp is a surface, and a piece is bedded onto it |
Any |
any plate at all. The exception, for a piece that WANTS the high ground: a crest, a spar tip or a stud |
A piece is stood up on the plate it lands on: on a surface it is turned onto that surface's own normal, so it lies flush however far the plate is raked; on a cone it stands up the cell, because a cone has no plane to lie on and the middle of the plate is its apex.
Do not use relief to mean this. Measured over 526 generated plates: Flat
and Brink are surfaces every time, Bevel, Ridge, Peak and Spur are cones
every time, and Step splits about in half - square-on to a raise it is a clean
ramp, on its diagonal it is a cone. A relief list written to mean "somewhere flat
to lie" is therefore wrong about a fifth of a hull in both directions. Name the
zone with relief and the seat with seat.
A hand-built ship has no seat anywhere on it. A small build is one cell thick
nearly everywhere, so every plate on it is a cone: the owner's five-cube L reads
0% seated against 58.6% on a generated hull. A kit whose every rule is Whole
therefore decorates a generated ship and leaves an editor build BARE. Give the
pieces that belong on the high ground seat: Any - every base kit does, for its
mast, whip, fin, stack or corner boss - and they carry the small builds.
The seven reliefs
What the top of a plate is shaped like, read off the derivation. Four fifths of a ship FALLS AWAY somewhere, and the last three say how many ways:
| relief | what it is |
|---|---|
Flat |
a flat panel - every boundary sample at the same height. Where a big piece fits |
Step |
the plate climbs structure standing proud beside it. A hard edge |
Ridge |
a crest across the plate: the tent a run of skin one cell wide comes out as |
Peak |
every sample on the floor, so the middle rides half a cell: a lone clad cell |
Bevel |
falls at ONE CORNER only: a panel with a corner taken off, and nearly a Flat |
Brink |
falls along ONE WHOLE SIDE: the straight edge of a hull. The only relief with an outward direction to turn to |
Spur |
falls TWO WAYS OR MORE: an outer corner, the tip of a spar, a saddle |
Start from what a hull offers
A GENERATED hull and a HAND-BUILT one offer almost opposite things. Measured, per
ship, on the three fixed seeds of the wfc_ships row and on a 7-9 part editor
build:
| subject | plates | flat | step | ridge | peak | bevel | brink | spur |
|---|---|---|---|---|---|---|---|---|
generated (the wfc_ships row) |
158-204 | 18-38 | 12-52 | 0-4 | 0 | 4-8 | 48-76 | 32-76 |
| hand-built (a 7-9 part editor ship) | 22-27 | 0 | 2-3 | 0-5 | 1-4 | 0 | 0 | 14-20 |
A rule written for flat panels lands on almost nothing, and on a hand-built
ship it lands on NOTHING AT ALL. A small build is one cell wide nearly
everywhere, so it has no flat plate, no bevel and no brink - only spurs, ridges
and studs, none of which is a seat. No
amount of patch rescues that: a floor over an empty set is still empty. Widen
the relief list, and give the piece seat: Any if it belongs on the high
ground.
Read the SPREAD, not the middle. Every bucket above swings by two or three times across three seeds of one generator, so a rule sized against one hull is not sized against the next. Tune against the logged tally below, on more than one subject.
Priority: one piece per plate
A plate takes AT MOST ONE piece, and the FIRST fixture in the list whose rule accepts it wins. So the order is a priority order: put the rare, specific pieces first and the common filler last.
Watch out for a rule that READS AS SPECIFIC AND IS NOT. Two measured examples, both of which carpeted a ship and starved every rule below them:
near_fitting: Some(1)sounds narrow. On a hull dense with drives and bays, something is beside a fitting nearly everywhere.max_border: Some(0)- "only at the end of a run" - admitted 126 of 132 plates, because on a broken-up hull almost every plate is the end of its own one-cell run. Pair it withmin_run: 2so a run has to be a run.
Both are visible rather than guessable. spawn_ship_skin and the build view log
each rule as taken of reach at debug, where reach is everything the filter and
the lattice admit before the share and before priority:
decoration mast x3 of 8, vent x5 of 7, block x29 of 94, blister x12 of 19
x0 of 78 is a rule that was starved or thinned away; x0 of 0 is a filter that
matches nothing this hull has. They look identical on screen and have opposite
fixes.
Density: one piece per block of hull
Every field above is per plate, and they MULTIPLY. A stride of 2 is a quarter of the surface, a share of 0.5 is half of that, and a relief filter is another fraction again - which reads as a field of pieces on a 150-plate generated hull and as one piece on a 20-plate hand-built one.
patch states the density in cells of ship instead. Set it to N and the rule is
guaranteed a piece in every block of N x N x N cells, per face, that it can
stand on at all: where the share already put one, nothing happens; where it did
not, the block's lowest-hashing eligible plate takes one.
- It is a FLOOR, never a cap. It only ever adds, and it never takes a plate another rule already claimed, so priority still means what it says.
- It drops the SHARE only. The filter and the lattice still hold, so a floor
piece lands on the same grid the rest of the rule does. Keep
patchat or abovestride. chance: 0.0with apatchset is the pure form: no share at all, exactly one piece per block. That is a density that reads the same at any hull size.- On a BIG hull a small
patchoutvotes the share - one per 3 cells over a fragmented hull is a lot of pieces. Size it against the ship, then check the logged tally.
Alignment, not noise
stride and align exist because alignment is what makes decoration read as
bolted on rather than as confetti. There is no random jitter and no rotation
freedom, deliberately - a piece is turned in quarter turns about the plate's own
outward axis, or not at all.
align |
what the piece's own +Z points down |
|---|---|
Free |
nothing. Right for anything with no long axis - a blister, a stud, a hatch |
Run |
the direction the surface RUNS, so a rib strip follows the spine it is on and a row of vents lines up with itself |
Outward |
the direction the surface FALLS, which is off the ship - a fairing leans out over the edge it stands on instead of lying along it |
The piece is turned ACROSS THE SURFACE it lies on, and then bedded onto it, so on
a raked plate its +Z comes out raked too: a fairing on a hull edge noses down
the slope rather than standing square out of it.
The two are square to each other. Outward is a rule for the falling plate:
Brink has a single outward cardinal, an outer corner leans on the diagonal
between its two sides, and a plate that does not fall one way is left unturned.
The scatter is deterministic
There is no RNG. Whether a plate takes a piece is decided by hashing the CELL it would stand in together with the fixture's id, so:
- the same ship always wears the same decoration, saved or not;
- the build view can show it live while a hull is dragged, without flicker;
- two ships built the same way are decorated the same way.
chance therefore does not mean "roll a die"; it means "the share of eligible
cells whose hash falls below this". Lowering it REMOVES pieces rather than moving
them.
patch is the one thing decided by a BLOCK of hull rather than by a single cell,
and the blocks are a fixed division of the ship's own cells. So growing a hull by
one cell leaves every piece outside the block that cell lands in exactly where it
was; inside that block the floor's own pick can move, if the new plate hashes
lower. Nothing shuffles across the ship.
The frame a greeble is authored in
A hull plate is one cell - the unit cube, out along +Y. A decoration model uses
that same frame:
+Yis out of the plate andy = 0is the mounting face. Nothing sits behind it.- The footprint is centred on the origin and stays inside half a cell, so a piece cannot spill across a plate seam onto its neighbour. A tall piece (a mast) reaches further up.
+Zis the piece's own long axis, whichalign: Runpoints down the run andalign: Outwardpoints off the ship.- Flat-shaded, low-poly, untextured, one primitive per flat colour - and under 200 triangles, because decoration is scattered many times over a hull.
The base game generates its own from committed JSON recipes
(scripts/gen-greebles.py, scripts/greeble-recipes/). A mod can ship .glb
files made any way it likes and reference them the same way.
Using a style
A ship wears a style by naming it, alongside the skin flag that asks for
cladding in the first place:
Ship((
id: "my_raider",
name: "My Raider",
hull: (
skin: true,
style: Some("my_raider_look"),
sections: [ ... ],
),
)),
Both fields are per HULL, so a raider hull and a civilian hull wear different looks and every scenario spawning them gets the right one. A style id nothing authored leaves the ship clad and BARE rather than falling back to another look - a missing mod is visible, not silently substituted.
The base game ships four authored looks and one piece of scaffolding:
| id | what it is |
|---|---|
industrial |
a working hull: exposed services, corrugation, radiators, safety-yellow paint on its edges |
armoured |
flat plate, a belt down every straight edge, sensor blisters |
civilian |
the racer's: pale satin paint, a cobalt livery rail, lit cabin windows, smooth fairings |
salvage |
the raider's: mismatched patches, weld beads, a lashed drum, a whip antenna |
placeholder |
scaffolding for the authored kits rather than a look to build on |
A ship that names no style flies undressed: built-in plate colours, no
decoration. The EDITOR's build view instead previews the first style the merged
content offers while none is picked, so the authored looks are listed before
the scaffolding. The editor lists every merged
style under its cladding toggle - a mod's look appears there beside the base
ones with nothing to register - and the wfc_ships example cycles the same list
with L, or takes --style <id>.
Drawing a CONTINUOUS line
Decoration cannot span cells: every piece stands on one plate. A band that
looks continuous is therefore a row of pieces, and it needs three things at
once - civilian_stripe is the worked example.
- The MODEL fills its cell along
+Z(a raised budget in the recipe), so neighbours butt together instead of leaving a gap. A piece is always centred on its plate, so a full-cell piece still cannot spill onto a neighbour. - The RULE takes every eligible plate:
stride: 1andchance: 1.0. A band with every other cell missing is a dashed line. align: Runturns each piece down the run, andrelief: [Brink]is the run worth following - the straight edge of a hull.
Do not try to cap the run's ends with min_border. border is the smallest
of the four in-plane walks, and a Brink has open space on one side, so a hull
edge reads border 0 along its whole length: a body rule on min_border: 1
matches nothing (x0 of 0) and the terminal takes the entire edge. min_border
is a rule for Flat and for nothing else.
Reading as UNPLANNED with no randomness
The scatter has no RNG in it, deliberately. salvage is the worked example of
what that makes hardest: decoration that reads as unplanned. Four devices carry
it, and none of them is jitter:
- three patch pieces in three materials, each gated on a DIFFERENT structural
reading (
near_fitting,min_depth, apatchfloor), because a hash is spatially incoherent and splitting one rule three ways bychancealone gives an even speckle rather than regions; - two of those pieces authored long on OPPOSITE in-plane axes with both aligned to the run, so neighbouring repairs cross at right angles while every piece stays square to the grid;
- every model authored OFF-CENTRE in its own footprint, since the scatter offers no jitter and a centred piece repeated is a tile;
- a weld bead built from four lumps of different size rather than from the
ribsprimitive, because even ribbing reads as machined.