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 placed on 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 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",
palette: (
top: (
color: Srgba((red: 0.4, green: 0.3, blue: 0.25, alpha: 1.0)),
roughness: 0.8,
metallic: 0.1,
),
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,
collider: (0.12, 0.38, 0.12),
),
],
greeble_rules: (
link_types: [
(id: "any", connects: ["any", "empty"]),
(id: "empty", connects: ["any", "empty", "spaced"]),
(id: "spaced", connects: ["empty"]),
],
greebles: [
(
id: "empty",
fixture: None,
weight: 6.0,
rotate: r#None,
link_points: (up: "empty", right: "empty", down: "empty", left: "empty"),
),
(
id: "antenna",
fixture: Some("antenna"),
weight: 0.3,
rotate: r#None,
link_points: (up: "spaced", right: "spaced", down: "spaced", left: "spaced"),
near: [(greebles: ["antenna"], radius: 4, factor: 0.0)],
max_count: Some(2),
),
],
),
)),
]
| 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 |
palette |
palette | required | what the plates are made of |
fixtures |
list | [] |
the decoration pieces: model, health and collider |
greeble_rules |
rules | required | where the pieces go - see Greeble rules |
palette
Two surfaces, both required:
| field | meaning |
|---|---|
top |
the shell face exposed to space, and normally the only one lit |
wall |
the shell side exposed at slopes, gaps and edges |
The third surface - the cell floor, against the section the plate clads - is not authored. It is never in shot, so it keeps the engine's own finish.
Each of the two is a finish:
| field | type | meaning |
|---|---|---|
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.
Both are required because the relationship BETWEEN them is most of a look. A style that set only one would be a style whose main move was decided by whichever default it happened to keep.
fixtures
| field | type | default | meaning |
|---|---|---|---|
id |
string | required | names the piece within its style. A greeble places it by this id |
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 |
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 |
There is no authored physics density. Every decoration stands at the engine's
DECOR_DENSITY of 0.25, the same as the skin's own cladding, and avian derives
the real mass from that and the collider's volume. Nothing a style says about
its look can make one ship heavier than another.
health and collider stay authored, and for different reasons. health
decides when a piece is shot OFF, and two greebles of a size are not equally
worth shooting off. collider is authored because a headless simulation never
loads the model its bounds could otherwise be read from.
Greeble rules
The engine reads a ship's skin as a grid of CELLS: one cell per plate, each with four edges, and across each edge the plate that continues the surface. The solve puts exactly one GREEBLE on every cell. A greeble is either a fixture or empty space, and it shows a LINK TYPE on each of its four edges. Two cells that share an edge must show link types that connect. The link rules do not read the shape of a plate: every plate is one cell. The solve only checks that a piece fits the plate it lands on (see The frame a greeble is authored in).
| field | type | default | meaning |
|---|---|---|---|
link_types |
list | required | every link type the greebles use, at most 64 |
greebles |
list | required | every greeble, with at least one fixture: None. An empty list places no decoration |
link_types
| field | type | meaning |
|---|---|---|
id |
string | the name greebles use for it |
connects |
list of ids | the link types it may share an edge with. Must be symmetric: if a lists b, b lists a |
A link type that does not connect to any link type of an empty greeble FORCES its neighbour: the cell across it cannot stay empty. A line link type that connects only to itself is the usual case.
greebles
| field | type | default | meaning |
|---|---|---|---|
id |
string | required | names the greeble in near terms |
fixture |
Option string |
required | the fixture it places, by id, or None for empty space |
weight |
float | required | relative chance against the other greebles a cell still allows. Above 0. Shared by all the turns |
rotate |
All, Half or r#None |
required | the quarter turns the solve may use: all four, none and the half turn, or none. Spell the last r#None, because a bare None reads as an empty Option |
link_points |
(up, right, down, left) |
required | the link type on each edge |
near |
list | [] |
range terms, below |
max_count |
Option integer |
None |
the most cells this greeble may take on one ship |
near
| field | type | meaning |
|---|---|---|
greebles |
list of ids | the greebles the term looks for |
radius |
integer | how far the term reaches, in cells along the grid. At least 1 |
factor |
float | multiplies this greeble's weight where one of greebles is in range. 0 is a hard ban, in both directions |
What fails
Every rule set is checked by content lint and again at load. A rule set that
fails is an error, and the style is dropped: every ship that names it flies
clad and bare. Each of these fails:
- a link type or a greeble declared twice, or more than 64 link types;
- a
connects, a link point or anearterm that names an unknown id; connectsthat is not symmetric;- a
weightthat is not a finite number above 0, afactorthat is not a finite number of at least 0, or aradiusof 0; - no greeble with
fixture: None; - a greeble whose
fixturenames no fixture in the style; - a fixture whose
collideris wider than one cell (xorzabove 1), because no plate can fit it; - a
factor: 0term on a greeble that a forcing link type can force, because the ban could empty a cell that must be filled; - a
factor: 0term or amax_counton an empty greeble, because empty space must stay available everywhere.
When a hull contradicts the rules
A hull is built by a player, so some rule sets cannot be met on every hull: a line that must continue has nowhere to end, or a cap runs out. The solve then starts again with a different hash, up to eight times. If every attempt fails it solves once more WITHOUT the greebles that carry a forcing link type, logs a warning, and leaves any cell that still contradicts empty. The ship is never refused and never crashes; it is dressed with less.
The solve is deterministic
There is no RNG. The seed is a hash of the style id, and every draw also hashes the cell it is for, 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 the whole hull changing;
- two ships built the same way are decorated the same way.
The structure is not in the seed. An edit changes the draws only where it changes the cells and their edges, so most plates far from the edit keep their pieces. Not all of them: the pick order is global, so a few plates anywhere on the hull can change.
When a ship is RE-CLAD after damage, the plates that stayed keep their pieces and only the new plates are solved. A link across the edge of the repair is not checked against the piece that stayed.
spawn_ship_skin and the build view log, at debug, how many plates each fixture
took:
decoration mast 2, vent 7, block 11, blister 9
A 0 on a big hull is a fixture its weight, its caps or its link types starve.
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 within half a cell of it each way, so a piece cannot spill across a plate seam onto its neighbour. A tall piece (a mast) reaches further up.
- The plate under a piece reshapes so its flat top covers the
colliderfootprint. Its chamfers and ramps get steeper, but its feet and corners stay, so it still meets its neighbours. A piece that reaches almost to an edge where the plate falls away would need a slope steeper than the engine allows. It does not fit that plate, and the solve leaves it off it. +Zpoints at the edge that holds the greeble'suplink point. A line greeble withlineonupanddowntherefore lies with+Zdown the line.- Flat-shaded, low-poly, untextured, one primitive per flat colour - and under 200 triangles, because decoration is placed 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 five looks:
| id | what it is |
|---|---|
industrial |
a working hull: exposed services, cranes and stacks, radiator clusters, safety-yellow hazard bands and ducts as lines |
armoured |
flat plate, applique clusters, strake lines, sensor blisters and masts |
civilian |
a private yacht's: pale satin paint, cobalt stripe and cabin window lines, dishes, fins and fairings |
salvage |
mismatched patch clusters, weld seam, chain and hose lines, a lashed drum, a whip antenna |
station |
station plate: antennae, beacons, radiator clusters, hatches, markings and window lines |
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. 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>.
Patterns the base kits use
Every base kit is built from the same four shapes of greeble. Read them in
crates/nova_authoring/src/base_content/styles.rs.
- Empty space. One
fixture: Nonegreeble onemptylink points, with the largest weight in the kit. It sets how bare the hull is: raise its weight and every fixture thins out together. - A filler.
anyon all four link points androtate: r#None. It can stand next to anything except a spaced piece. Hatches, vents and stencils. - A spaced piece.
spacedon all four link points, which connects only toempty, so nothing stands beside it. Add anearban on itself and amax_countso a ship carries one or two, apart. Masts, dishes, cranes. - A cluster. A filler with a
nearterm on itself at radius 1 and a factor above 1, so one piece makes the next one beside it more likely. Radiators, applique, salvage patches. - A line. A line link type that connects only to itself, a body greeble
with it on
upanddownandanyon the sides,rotate: Half, and an END greeble with it onuponly androtate: All. Both place the same fixture. Because the line link type forces its neighbour, a started line runs until an end greeble closes it, so a line never stops at a gap. Use a second line link type (line_2) for a second line fixture, so the two never join.
link_types: [
(id: "any", connects: ["any", "empty"]),
(id: "empty", connects: ["any", "empty", "spaced"]),
(id: "spaced", connects: ["empty"]),
(id: "line", connects: ["line"]),
],
greebles: [
// ... the empty greeble ...
(
id: "stripe",
fixture: Some("stripe"),
weight: 1.2,
rotate: Half,
link_points: (up: "line", right: "any", down: "line", left: "any"),
),
(
id: "stripe_end",
fixture: Some("stripe"),
weight: 0.3,
rotate: All,
link_points: (up: "line", right: "any", down: "any", left: "any"),
),
],
The model of a line piece fills its cell along +Z, so neighbours butt
together. A piece is always centred on its plate, so a full-cell piece still
cannot spill onto a neighbour.
Reading as unplanned with no randomness. salvage is the worked example.
Its patches are clusters in three materials, so they gather in regions instead
of an even speckle; its weld seam, chain and hose are three separate lines; and
every model is authored off-centre in its own footprint, so a piece repeated is
not a tile.