diff --git a/docs/aetheria-release-map.md b/docs/aetheria-release-map.md index 62309781a..53a77c6c6 100644 --- a/docs/aetheria-release-map.md +++ b/docs/aetheria-release-map.md @@ -581,7 +581,12 @@ S2's owed checks, the merge, the Blender package action and S3. S5 stays with th variants campaign. The cuts are typed specs in the mind (`ships-merge`, `ships-mounts`, `ships-addon-package`, `ships-addon-frame`, `ships-addon-mounts`, `ships-player`), and the forks are questions (`ships-merge-gate`, -`release-hull-home`, `s3-player-build`). +`release-hull-home`, `s3-player-build`). Two rulings of 2026-10-03 reshape the +add-on and player cuts and add a material strand: `hull-runtime-materials` and +`thrusters-radiators-are-meshes`. They are mapped in "Hull materials, mount +meshes and per-hull prep" below, with the cuts `ships-hull-material`, +`ships-hull-livery` and `ships-addon-paint` and the questions `hull-paint-mask` +and `hull-livery-scope`. Anchors are against the lane tip `a93625c2`, read with `git show` on 2026-10-03. For files the lane does not touch, they hold on `origin/master` `f1dee184` too. @@ -789,12 +794,14 @@ that needs her eyes takes one click at the 3D cursor. - The add-on binds the collection, turns the long axis to −Y, scales the mesh to length × 2 m, rasterises a draft grid from the top-down silhouette, and shows the grid as an overlay. -3. Cleanup, the variable step: delete Tripo debris and check the nose. Decimate if - the triangle warning fires. -4. Mounts. With the 3D cursor on each nozzle, gun and radiator, press **Add - Thruster**, **Add Weapon** (Energy, Ballistic or Launcher) or **Add Radiator**. - - Each creates the anchor object (an emitter disc, a mount empty with one muzzle, - or a panel, or marks the selected object). +3. Cleanup and paint, the variable steps: see "Hull materials, mount meshes and + per-hull prep" below (prep steps P3 to P5). +4. Mounts: + - Thrusters and radiators are meshes (ruling `thrusters-radiators-are-meshes`). + Select the nozzle-exit or radiator faces in Edit Mode and press **Add + Thruster** or **Add Radiator**, or tag a selected mesh object. + - Weapons stay points: with the 3D cursor on each gun, press **Add Weapon** + (Energy, Ballistic or Launcher). - Each also creates the hardpoint row on the cell under it, with a 1×1 footprint. 5. Panel: toggle cells, add the internal hardpoints (reactor, shield, sensors and so on, which carry no anchor), and set footprints. @@ -817,18 +824,9 @@ Steps 2, 4 and 6 replace the hand work that M8 shows she has started. The `aetheria.id` of every node is its anchor id, so `ModelNodeId` equals the anchor `Id`. Node ids are written by the add-on and never typed by her. -**Material and livery slots.** Materials have one owner per role: -- **The GLB owns the hull's look.** glTF PBR, imported by glTFast. -- **The template owns the game's materials, by anchor role:** - - `map-icon` gets `MapIcon`, which exists; - - the `hull-collider` renderer is forced off, which exists; - - `radiator-mesh` gets the new `Radiator` material (`ships-player`, M9). -- **Thruster emitters** keep their glTF material. She may give them an emissive one. -- **Livery is deferred** (follow-up `ship-livery`). The release bar is one hull per - faction concept, so each hull's own texture already carries its faction look. A - faction tint needs a mask texture per hull, and Tripo does not produce one. The - slot can be added later without touching packages: a named mask, read by a - presenter with a `MaterialPropertyBlock`. +**Material slots.** The material owners are in "Hull materials, mount meshes and +per-hull prep" below. Ruling `hull-runtime-materials` superseded the deferral of +livery recorded here before. **Colliders.** Hits are fire control's (R8). The hull collider serves only ship-on-ship contact and is convex (`ShipModShips.cs:110`), so a generated convex @@ -844,7 +842,308 @@ the generator emits no more. revision guard, and the per-hull checklist in the authoring doc. Each fits well under 200k Hands tokens. The core alone already makes a valid -package from hand-placed objects. +package from hand-placed objects. Revision 2 of each (2026-10-03) carries the two +new rulings; the section below says what changed. + +### Hull materials, mount meshes and per-hull prep + +Two operator rulings of 2026-10-03 drive this section. +- `hull-runtime-materials`: "I expect I'll want to do quite some cleanup and prep + for the meshes. The geometry is still not ideal, and we want more runtime + material control than just a single baked map. See how MechWarrior does it, for + example." +- `thrusters-radiators-are-meshes`: "Note that both thrusters and radiators should + be meshes, the former doesn't get rendered but is used as an emission surface". + +The prior-art survey is `F:\Projects\aetheria-hull-materials-prior-art.md` +(Eyes, 2026-10-03). Anchors below are against `80de88f2`, the tip of the +in-flight `eureka/aetheria-release-ships-mounts`. The add-on is unchanged there +since `a93625c2`. + +#### Body facts + +**M13. Mod hulls render outside the game's ship lighting and fade.** +- `ShipModVisual.LoadAsync` builds `new GltfImport(...)` with no material + generator (`ShipModVisual.cs:45`). glTFast's default Built-In generator therefore + picks `glTF/PbrMetallicRoughness` for every hull material. +- Prefab hulls paint with `Aetheria/GlowFade` (`Assets/Shaders/GlowFade.shader`; + `Car Paint White.mat` and `Cockpit.mat` use it). It does two things a glTF + shader does not: + - it lights by the volumetric ambient (`noambient`, `VolumeSampleColorSimple` in + `vert`, `:35` and `:67-72`); + - it dithers in and out by `_Fade` (`:84-88`). +- `EntityInstance.Awake` gives each material with `_Fade` a per-entity instance it + fades. Every other submesh is swapped to `InvisibleMaterial` and only shown by + `ShowUnfadedElements` (`EntityInstance.cs:86-99`, `:120-131`). So a mod hull pops + where a prefab hull dissolves, and it is lit differently. +- Nothing tints any ship today. `Faction.PrimaryColor` and `SecondaryColor` + (`Corporations.cs:35-38`) are read only by the sector map (`SectorMap.cs`). + +**M14. glTFast keeps meshes readable and passes vertex colours through unchanged.** +Read from the package source (`com.unity.cloud.gltfast@11ddc2436f97`): +- `MeshGenerator.cs:539` calls `UploadMeshData(false)`, so meshes stay readable. + The collider (`ShipModShips.cs:103`) and a particle mesh shape need that, so + M10's readability doubt is closed for the importer. `ships-player` still checks + it in a built player. +- `VertexBufferColors.cs:25,88` stores `COLOR_0` as `float4` with no colour-space + conversion. The project is in Linear colour space (`m_ActiveColorSpace: 1`). +- `IMaterialGenerator.GenerateMaterial(MaterialBase, IGltfReadable, bool)` sees + every core slot: `PbrMetallicRoughness.BaseColorTexture` and + `MetallicRoughnessTexture`, `NormalTexture`, `OcclusionTexture`, + `EmissiveTexture`, each with `index` and `texCoord` (`Schema/Material.cs:48-77`, + `TextureInfo.cs:42-49`). `IGltfReadable.GetTexture(int)` returns the texture. + +**M15. Blender exports a named colour attribute exactly.** Probe, Blender 5.2.2, +`--background --factory-startup`: +- Setup: a quad with a `FLOAT_COLOR` corner attribute `aetheria.paint` set to + `(1, 0.5, 0.25, a)`, with `a` = 0, 0.5, 1, 0.5. +- Export: `export_vertex_color='NAME', export_vertex_color_name='aetheria.paint', + export_all_vertex_colors=False`. +- Result: one `COLOR_0`, unsigned-short normalised `VEC4`, values exact to 1e-5, + alpha kept. +- With `export_vertex_color='ACTIVE'`, the exporter wrote every colour attribute + (`COLOR_0` and `COLOR_1`), and `COLOR_0` was all ones. The add-on must name the + attribute. + +**M16. The Tripo hulls, measured.** A read-only probe of a copy of `Quiet.blend`: + +| Hull | Triangles | Face-connected parts | Largest part's share of faces | Parts under 2% of hull length | Non-manifold edges | UV islands | +|---|---|---|---|---|---|---| +| Headliner | 50,008 | 189 | 33% | 23 (647 faces) | 6,145 | 6,237 | +| Dexter Quiet | 9,614 | 12 | 87% | 8 (124 faces) | 524 | 1,386 | +| Sinister Quiet | 10,880 | 12 | 88% | 7 (114 faces) | 356 | 1,525 | + +- Headliner is a kitbash. 124 of its parts are 2 to 10% of its length, so they are + greebles, not debris. Deleting small parts automatically would strip its + detail. +- The UV atlases are confetti: 1,400 to 6,200 islands in one 4096² image. A mask + painted or filtered in that texture space is unusable, and a clean texture mask + would need a fresh unwrap and a rebake per hull. +- K-means (CIELAB, a 512² sample) of the albedos: + - Headliner's is colourful. With k = 6, the clusters are dark navy (43% of the + texels), mid grey (21%), beige (13%), lavender (11%), orange (7%) and blue + (4%). + - Quiet's is near-greyscale (a* and b* within ±6): light panels (44% at L 69 to + 79), darks and teal accents. + - Within-cluster lightness spread is 3 to 12 L. That spread is the baked shading + and detail a tint must keep. +- Probe scripts: `kmeans.py`, `meshstats.py`, `parts.py`, `vcol.py` in this pass's + scratch. Their numbers are quoted here. + +**M17. Thruster and radiator mounts are already meshes at runtime.** +- `ShipModShips.Assemble` requires a `MeshRenderer` under both + (`ShipModShips.cs:87-92`, `:128-137`). +- On `80de88f2`, `ships-mounts` hides the thruster's mesh behind the template's + `Invisible` material (`:130`, commit `da2a299b`). +- `ShipInstance.SetEntity` hands that renderer to the exhaust particle system as + its shape (`ShipInstance.cs:68-71`). Unity's documentation says a mesh shape + emits along the surface normal. +- The GLB rule is not checked before Unity. `ShipModCatalog.Bind` checks only that + each anchor's node exists (`ShipModCatalog.cs:176-180`), and + `ship-authoring validate` reads the records alone + (`ShipAuthoringCommands.cs:32-37`). +- The add-on side is still the r1 spec: a disc and a panel at the 3D cursor. + +#### The hull material model + +The model buys MechWarrior and Warframe-class control with the fewest new +surfaces: +- three paint colours chosen at runtime; +- a finish per paint; +- wear on edges and grime in cavities; +- emissive lights; +- glowing radiators; +- livery from the faction, with player paint and patterns as a later step. + +It is written for the recommended option of question `hull-paint-mask`. + +**What a package carries.** +- **The GLB's core PBR slots:** + - base colour (Tripo's albedo); + - metallic-roughness; + - normal; + - occlusion; + - emissive texture and factor. + + Whatever Tripo or the operator's bake provides is used, and missing slots fall + back to factors. +- **`COLOR_0` on the hull meshes, the paint mask.** + - RGB is the weight of the primary, secondary and accent paint. Black is bare: + the GLB's own surface shows. + - Alpha is signed convexity: 0.5 is flat, toward 1 on convex edges (where paint + wears off) and toward 0 in cavities (where grime collects). + - RGB is authored per face in Blender. Alpha is derived at Package. +- **Blender material slots become glTF materials.** These are surface kinds, such + as bare metal, glass and lights, each with its own factors. A light is a material + with an emissive factor. No texture is needed. +- **`ShipAuthoring.Paint` (key 5, a `HullPaint`), typed in the package.** + - The factory `Livery`: three colours, a metallic and smoothness per slot, wear + and grime. + - `RegionLuminance`: each region's mean albedo luminance, derived at Package. + - Null means an unpainted hull. It renders with the GLB's look, and still fades + and is lit like a prefab hull. + +**What the game owns.** +- One shader, `Aetheria/Hull`, built on `GlowFade`'s fade, dither and volumetric + ambient. Its rules: + - Painted albedo is the slot colour × saturate(lum(base) / region luminance). + The factory livery, whose colours are the region means, therefore reproduces + the original look. A new colour keeps the original's panel lines and shading. + - Wear: where alpha is above 0.5 and a tiling noise is under `_Wear`, paint + chips to the GLB's surface. + - Grime: below 0.5, `_Grime` darkens and roughens. + - Painted metallic and smoothness come from the slot's finish. + - It has no `shader_feature` keywords. Its only variants are `GlowFade`'s + `multi_compile` set, so build stripping cannot drop one. +- `HullMaterialGenerator`, a glTFast `IMaterialGenerator`, builds every hull + material as a clone of `ShipModTemplate.Hull` with the GLB's slots. No glTF + shader is needed at runtime, so `ships-player` r1's variant collection is + deleted. +- `ShipModShips.ApplyPaint` sets the resolved livery on each hull renderer through + a `MaterialPropertyBlock` in `ShipInstance.SetEntity`. Property blocks do not + survive `Instantiate`, so a prototype cannot carry them. +- Livery resolution, as one pure function, `HullPaint.Resolve`: the owner + faction's `Livery` (`Faction` key 16, cut `ships-hull-livery`) when set, else + the hull's factory livery. Region luminance is always the hull's own. + +**Role materials and the two mount meshes** (ruling +`thrusters-radiators-are-meshes`): +- **Thruster:** a mesh carrying the invisible material (done in `ships-mounts`). It + is the exhaust's emission shape, and its normals point aft. +- **Radiator:** a mesh rendered with the template's `Radiator` material. + `EntityInstance` drives its `_Emission` with temperature (`ships-player`). +- **Weapons** stay point anchors. +- **Map icon and collider:** as before. + +**Out of release scope, as follow-ups:** +- `hull-decals`: insignia and damage decals. +- `hull-damage-wear`: wear driven by hull durability. The feedback strand owns it. +- `hull-player-paint`: player paint and patterns, unless question + `hull-livery-scope` rules otherwise. + +#### What the operator authors per hull, and what is derived + +| Step | Operator authors | Derived automatically | +|---|---|---| +| P1 Tripo export | One test regeneration with PBR on (below) | — | +| P2 New Ship | id, name, reference hull, length | orientation, scale, draft grid, `create --like` | +| P3 Clean | which selected debris to delete; decimation if warned; normals fixed where wrong | debris and enclosed-part selection, mesh report (tris, parts, non-manifold edges) | +| P4 Paint regions | each cluster's role; face fixes with Primary, Secondary, Accent and Bare; livery colour tweaks | face clusters from the albedo, default roles, factory colours = cluster means | +| P5 Surface kinds | material slots for glass and lights, with their factors | — | +| P6 Mounts | the faces for each thruster and radiator, the 3D cursor for each gun | emitter normals turned aft, hardpoint rows on the cells beneath | +| P7 Layout | internal hardpoints, cell touch-ups | — | +| P8 Package | — | recentring, collider, map icon, shield, tractor, wear and grime alpha, region luminance, texture cap, GLB, records, validation | +| P9 Look | dock, `give`, fly | — | + +**Rejected for the release path: retopology and rebaking.** +- Decimation keeps Tripo's UVs, and the paint mask lives on faces, so no step needs + a new unwrap. +- A retopologised hull baked Selected to Active (Blender manual, Cycles baking) + stays available for a hull whose silhouette or shading needs it. +- The vendor claims for that path are 2 to 4 hours a hull (survey §4). + +**Time budget per hull.** These are estimates, not measurements. The operator +checks in `ships-addon-frame`, `ships-addon-mounts` and `ships-addon-paint` record +the real numbers. + +| Step | Quiet-class (10k triangles, 12 parts) | Headliner-class (50k, 189 parts) | +|---|---|---| +| P2 | 2 min | 2 min | +| P3 | 10 min | 30 to 45 min | +| P4 | 15 min | 30 min | +| P5 | 5 min | 10 min | +| P6 and P7 | 15 min | 20 min | +| P8 and P9 | 10 min | 10 min | +| **Total** | **about 1 hour** | **about 1.5 to 2 hours** | + +Ten package hulls (`content-bar-one-per-concept`) cost about 12 to 18 hours of +operator time with the tooling. The add-on cuts own what shrinks it: +- debris selection (`ships-addon-frame`); +- face-based mounts (`ships-addon-mounts`); +- clustering, roles and derived wear (`ships-addon-paint`). + +**P1, the Tripo export check: a step for the operator to try.** +- Tripo's API defaults to `pbr=true`, which yields base colour, metallic, + roughness and normal maps. Texture version `v3.5-20260815` adds `delight` + (default true), which removes baked lighting. +- The hulls in `Quiet.blend` arrived with base colour only, and why is unknown. +- Suggested try: regenerate one hull (Dexter Quiet) on the site and through the + API, with PBR on and delight on, then import the GLB into Blender. + - If the metallic-roughness and normal maps are present, the generator uses them + with no further work. + - A delit albedo also makes the region-luminance tint cleaner. + - If the site only exports base colour, the API is the route for the release + hulls. +- Nothing in the cuts waits on this. + +#### Cuts + +- **`ships-hull-material`** (new): `Aetheria/Hull`, `HullMaterialGenerator`, + `HullPaint` and `Livery`, `ShipAuthoring.Paint`, the `COLOR_0` package rule and + `ApplyPaint` with the factory livery. +- **`ships-hull-livery`** (new): `Faction.Livery` and its resolution. It waits on + `hull-livery-scope`. +- **`ships-addon-paint`** (new): Suggest and Apply Regions, the role buttons, + derived alpha and luminance, the named-attribute export and + `ship-authoring paint`. +- **`ships-addon-package` r2:** + - `validate` also reads the GLB (`ReadPackage`); + - `Bind` refuses a thruster or radiator anchor whose node has no mesh. +- **`ships-addon-frame` r2:** Select Debris and the mesh report. +- **`ships-addon-mounts` r2:** + - Add Thruster duplicates selected faces, with normals aft; + - Add Radiator separates them; + - either one can tag a selected mesh object; + - no primitives at the cursor. +- **`ships-player` r2:** + - the glTFast variant collection is deleted; + - a no-`glTF/`-shader check; + - every radiator submesh gets the radiator material; + - an emitter readability probe; + - it is no longer blocked (ruling `adopt-build-cut-1`). + +Order: `ships-mounts`, then `ships-addon-package`, then `ships-addon-frame`, then +`ships-addon-mounts`. `ships-hull-material` can run beside the add-on cuts, after +`ships-mounts`. `ships-addon-paint` needs both lines; `ships-hull-livery` and +`ships-player` need `ships-hull-material`. + +#### Model page rows + +| Kind | Identity | Lifecycle | Authority | +|---|---|---|---| +| Hull paint | `ShipAuthoring.Paint` in the package | Factory livery authored in Blender's panel; region luminance re-derived every Package | `ship-authoring paint` writes it; `ShipAuthoringStore.Validate` judges it | +| Paint mask | `COLOR_0` on the GLB's unanchored meshes | RGB authored per face; alpha re-derived every Package | Blender's `aetheria.paint` attribute; `Bind` requires it when `Paint` is set | +| Faction livery | `Faction.Livery` in `Aetheria.cc` | Authored in the inspector; null keeps factory paint | The catalog | +| Hull material | One clone of `ShipModTemplate.Hull` per glTF material | Built at preload, cache only | `HullMaterialGenerator` | + +#### Rationale + +**Why vertex colours rather than a mask texture** (the recommendation in +`hull-paint-mask`): +- M16 settles it. A texture mask on Tripo's confetti atlases needs a new unwrap and + rebake per hull, which is the costliest prep step there is. +- Hard-surface paint follows panels, and panels are faces, so face-level regions + lose little. +- Star Citizen already carries wear in vertex alpha, and its tint palette is three + colours (survey §2). +- A sidecar texture can be added later as a second source for one hull that needs + stripes inside a face. That adds a field, not a rebuild. + +**Why a generator instead of post-import material swaps.** +- glTFast calls the generator once per glTF material, with the slots already + parsed. +- Swapping afterwards would leave glTF shaders as build inputs and would decide + materials by renderer, which Assemble is forbidden to do by name. + +**Why the tint divides by region luminance.** Warframe asks artists for about 50% +grey under tinted areas (survey §2). Tripo's albedo is not that: Headliner's navy +sits at L 16. Dividing by the region's own mean normalises any albedo without +rewriting the texture, and the factory livery round-trips to the original look. + +**Why thrusters duplicate faces but radiators separate them.** +- A thruster's mesh is never drawn, so the hull must keep its nozzle. +- A radiator's faces are drawn with the radiator material. Leaving them in the hull + would draw the surface twice and fight over depth. ### S3: the first playable mod ship in a built player