diff --git a/docs/MOLECULES.md b/docs/MOLECULES.md index 4935947..dcdf2f3 100644 --- a/docs/MOLECULES.md +++ b/docs/MOLECULES.md @@ -5,8 +5,9 @@ systems, their economy is Lane C's, object art is Lane D's — this document is three, plus a working renderer they can take or leave. Fly it: `web/dev/laneA_molecules.html` · code: `web/js/world/molecule.js` · -evidence: `docs/shots/laneA/round2_molecule_pickups.png`, `round2_molecules_in_canal.png`, -`round2_molecules_truescale.png` +evidence: `docs/shots/laneA/round2_molecule_pickups.png` (the set + roles), +`round2_molecule_materials.png` (flat vs matcap vs space-filling, in the real canal), +`round2_molecules_in_canal.png`, `round2_molecules_truescale.png` --- @@ -46,14 +47,69 @@ That's ART_BIBLE's synthetic-scanner fiction paying rent: the ship identifies a colours it in. → **Lane E**: this is a free HUD language. Accent a readout in an element's CPK colour and it belongs to that compound with no legend. -## Why procedural, not MODELBEAST — and where MODELBEAST *is* right +## The division of labour: procedural geometry, generated materials -I'm authorised to burn GPU on this and I'm not going to, for one reason: **a molecule's shape -is already known exactly.** Glucose is a hexagonal ring because it is a hexagonal ring. FLUX + -TRELLIS would give a plausible blob, in 3–8 minutes, that a chemist clocks as wrong instantly -and that can never be re-derived. Ball-and-stick from an atom list is exact, rebuilds in -milliseconds, and is **one draw call per molecule** (measured: 7 molecules = 7 draws, 26.5k -tris total; glucose alone is 3.7k tris / 1 draw). +**MODELBEAST makes the materials. Procedural makes the shapes.** Both halves matter and they +are not in competition — the first version of this doc framed it as "don't use the GPU", which +was the wrong axis and is corrected below. + +**Geometry is not a generation problem, because a molecule's shape is already known exactly.** +Glucose is a hexagonal ring because it is a hexagonal ring. FLUX + TRELLIS would give a +plausible blob, in 3–8 minutes, that a chemist clocks as wrong instantly and that can never be +re-derived. Ball-and-stick from an atom list is exact, rebuilds in milliseconds, and is **one +draw call per molecule** (measured: 7 molecules = 7 draws, 26.5k tris total; glucose alone is +3.7k tris / 1 draw). + +**Material is exactly a generation problem**, and it's what separates a diagram from an object +— see the next section. + +### What MODELBEAST *did* do: the materials (round 2, second pass) + +Geometry is exact and free. **Material is where the GPU earns its keep** — and it's the whole +difference between a textbook diagram and a game object. GUTS has no real-time lights by law, +so a **matcap** (a lit-sphere image) is the only way to make something look *made of* anything. + +Five greyscale matcaps, flux_local, **69 seconds, $0.00, 68 KB total**: + +| matcap | elements | what it does | +|---|---|---| +| `mol_glass` | O, N | **the big win.** Oxygen becomes a deep red glass marble with internal glow. | +| `mol_matte` | C | graphite soot. Carbon is scaffold, and now it reads as scaffold. | +| `mol_chrome` | Co, Fe, Na | polished metal — B₁₂'s cobalt is a chrome bearing in a cage. | +| `mol_molten` | P | ATP's phosphate tail is incandescent, and **pulses** (`uTime`, free). | +| `mol_pearl` | H | soft satin white; hydrogens stop shouting. | + +**The trick that makes it cheap is Lane D's own law, applied to spheres**: author the +*luminance*, tint in-shader. `derive_maps` greyscales every matcap already, so one glass ball +serves oxygen *and* nitrogen at their own CPK hues. Five balls dress the whole periodic table +we care about, and they pack into one strip atlas indexed per-vertex by `aMat` — so **a +molecule is still exactly one draw call** no matter how many materials its atoms are made of. +`?matcap=0` falls back to the built-in fake-lit path: the assets-optional law holds. + +Two things that only showed up by rendering it, both fixed: +- **Tint × luminance darkens twice.** CPK carbon is already 0.4 and a matte ball averages 0.5, + so soot × soot vanished — the first pass sank glucose, caffeine and the cobalt into the + background. Floored the matcap at 0.22, the same floor the fallback's key light has always + had. +- **The molten matcap was my own bad prompt**: I asked for an "incandescent white hot *core*" + and got exactly that — a black ball with a hot spot, so the phosphorus lost its orange + entirely. Re-rolled asking for the *whole sphere* to glow. Lesson for the kit, next to the + organ/tissue one: **a matcap is a luminance lookup, so a dark-dominant ball makes a + dark-dominant object.** Ask for bright, always. + +### What the canal decided: space-filling wins for pickups + +`buildMolecule(id, {repr})` now does `'ball-stick'` (default) or `'space-filling'` — every atom +at its real van der Waals radius, no sticks, atoms merging into one solid lump. A/B'd at real +pickup size against the real L2 wall, same camera (`round2_molecule_materials.png`): + +**Ball-and-stick goes spindly and starts dissolving. Space-filling holds its silhouette — and +it is CHEAPER** (21k vs 26k tris across the set: no bond cylinders). It's also more true, since +that *is* how a molecule occupies space. + +→ **Recommendation to B**: `space-filling` for anything in flight, `ball-stick` for hero, UI, +the medal card and the collect close-up — where the chemistry is legible and worth reading. +The obvious LOD (blob far, sticks near) is untested and I'm not claiming it. **The split, and it's a principle, not a preference:** @@ -132,5 +188,14 @@ fixed box — B's call.) but nobody should mistake this table for a structure database later. - **The B12 is a simplified corrin core** — the real thing has a nucleotide tail and far more side chains. It reads as "jewel in a cage", which is the job. -- **Not performance-tested at pickup density.** 7 on screen is 7 draws / 26k tris. Fifty would - want instancing or a merged batch, and neither exists yet. +- **Not performance-tested at pickup density.** 7 on screen is 7 draws / 26k tris (13 draws / + 87k with the canal behind them). Fifty would want instancing or a merged batch, and neither + exists yet. +- **The matcap atlas is built at runtime from a canvas.** Fine, but it means a one-frame hitch + on first build and it bypasses `assets.matcap()` (it reads `assets.get('matcaps', …).url` — + the documented floor — and loads the images itself, because an atlas needs pixels and + `matcap()` hands back a `THREE.Texture` that may not have decoded yet). If this is adopted, + baking the atlas offline in `derive_maps.py` would be tidier and D may prefer it. +- **`repr: 'space-filling'` hides the bonds by definition**, so a molecule's *chemistry* stops + being readable — that's the trade for silhouette, and it's why ball-stick stays the default + for anything the player is meant to look AT rather than fly at. diff --git a/docs/shots/laneA/round2_molecule_materials.png b/docs/shots/laneA/round2_molecule_materials.png new file mode 100644 index 0000000..9162247 Binary files /dev/null and b/docs/shots/laneA/round2_molecule_materials.png differ diff --git a/pipeline/batch_textures.json b/pipeline/batch_textures.json index 08de48f..b1b699d 100644 --- a/pipeline/batch_textures.json +++ b/pipeline/batch_textures.json @@ -1,4 +1,69 @@ { + "matcap_mol_chrome": { + "gen_seconds": 12.6, + "guidance": 3.5, + "kb": 8, + "kind": "matcap", + "model": "flux2-klein-4b", + "prompt": "spherical material study ball, polished chrome metal, mirror finish, bright hard specular highlights, studio black background", + "seam": null, + "seed": 1412, + "size": 512, + "steps": 4, + "tile": null + }, + "matcap_mol_glass": { + "gen_seconds": 12.9, + "guidance": 3.5, + "kb": 6, + "kind": "matcap", + "model": "flux2-klein-4b", + "prompt": "spherical material study ball, polished translucent glass marble, deep internal glow, single sharp specular highlight, studio black background", + "seam": null, + "seed": 1410, + "size": 512, + "steps": 4, + "tile": null + }, + "matcap_mol_matte": { + "gen_seconds": 15.1, + "guidance": 3.5, + "kb": 33, + "kind": "matcap", + "model": "flux2-klein-4b", + "prompt": "spherical material study ball, matte graphite, fine powdery surface, soft diffuse shading, no shine, studio black background", + "seam": null, + "seed": 1411, + "size": 512, + "steps": 4, + "tile": null + }, + "matcap_mol_molten": { + "gen_seconds": 10.1, + "guidance": 3.5, + "kb": 37, + "kind": "matcap", + "model": "flux2-klein-4b", + "prompt": "spherical material study ball, glowing hot lava, the entire sphere uniformly incandescent and bright, even emissive glow, studio black background", + "seam": null, + "seed": 1413, + "size": 512, + "steps": 4, + "tile": null + }, + "matcap_mol_pearl": { + "gen_seconds": 10.8, + "guidance": 3.5, + "kb": 4, + "kind": "matcap", + "model": "flux2-klein-4b", + "prompt": "spherical material study ball, white pearl, soft satin subsurface sheen, gentle highlight, studio black background", + "seam": null, + "seed": 1414, + "size": 512, + "steps": 4, + "tile": null + }, "matcap_tissue_wet": { "gen_seconds": 8.9, "guidance": 3.5, diff --git a/pipeline/gen_textures.py b/pipeline/gen_textures.py index e3138c0..8c7945e 100644 --- a/pipeline/gen_textures.py +++ b/pipeline/gen_textures.py @@ -122,6 +122,41 @@ MATCAPS = { p=("spherical material study ball, wet translucent organic tissue, subsurface glow, " "studio black background"), seed=1401, stem_override=True), + + # ── molecule material families (Lane A, round 2) ───────────────────────────────────── + # GUTS has no real-time lights (ART_BIBLE), so a matcap is the ONLY way to make an object + # look *made of* something. These are the molecule pickups' materials — see docs/MOLECULES.md. + # + # Why families and not one per element: derive_maps greyscales every matcap anyway + # (`load_gray`), and Lane D already proved the pattern on the walls — author the LUMINANCE, + # let the shader apply the tint. So one greyscale "glass" ball serves oxygen AND nitrogen at + # their own CPK hues, and five balls dress the whole periodic table we care about. The CPK + # colour keeps the science legible; the matcap decides whether it reads as a gem, a metal or + # a lump of soot. + "matcap_mol_glass": dict( + p=("spherical material study ball, polished translucent glass marble, deep internal " + "glow, single sharp specular highlight, studio black background"), + seed=1410, stem_override=True), + "matcap_mol_matte": dict( + p=("spherical material study ball, matte graphite, fine powdery surface, soft diffuse " + "shading, no shine, studio black background"), + seed=1411, stem_override=True), + "matcap_mol_chrome": dict( + p=("spherical material study ball, polished chrome metal, mirror finish, bright hard " + "specular highlights, studio black background"), + seed=1412, stem_override=True), + "matcap_mol_molten": dict( + # v1 asked for an "incandescent white hot CORE" and got exactly that: a black ball with + # a hot spot. A matcap is a LUMINANCE lookup that gets multiplied by the CPK tint, so a + # dark-dominant ball makes a dark-dominant atom — ATP's phosphates came out as tiny + # white specks with no orange in them. Ask for the whole sphere to glow, not a core. + p=("spherical material study ball, glowing hot lava, the entire sphere uniformly " + "incandescent and bright, even emissive glow, studio black background"), + seed=1413, stem_override=True), + "matcap_mol_pearl": dict( + p=("spherical material study ball, white pearl, soft satin subsurface sheen, gentle " + "highlight, studio black background"), + seed=1414, stem_override=True), } PROMPTS = {**{k: v for k, v in WALLS.items()}, **MATCAPS} diff --git a/web/assets/gen/matcap_mol_chrome.webp b/web/assets/gen/matcap_mol_chrome.webp new file mode 100644 index 0000000..96dccfd Binary files /dev/null and b/web/assets/gen/matcap_mol_chrome.webp differ diff --git a/web/assets/gen/matcap_mol_glass.webp b/web/assets/gen/matcap_mol_glass.webp new file mode 100644 index 0000000..5a2bde4 Binary files /dev/null and b/web/assets/gen/matcap_mol_glass.webp differ diff --git a/web/assets/gen/matcap_mol_matte.webp b/web/assets/gen/matcap_mol_matte.webp new file mode 100644 index 0000000..d6c5eb6 Binary files /dev/null and b/web/assets/gen/matcap_mol_matte.webp differ diff --git a/web/assets/gen/matcap_mol_molten.webp b/web/assets/gen/matcap_mol_molten.webp new file mode 100644 index 0000000..98b3fc2 Binary files /dev/null and b/web/assets/gen/matcap_mol_molten.webp differ diff --git a/web/assets/gen/matcap_mol_pearl.webp b/web/assets/gen/matcap_mol_pearl.webp new file mode 100644 index 0000000..5bdc09b Binary files /dev/null and b/web/assets/gen/matcap_mol_pearl.webp differ diff --git a/web/assets/manifest.json b/web/assets/manifest.json index 3cc7228..f0efcc4 100644 --- a/web/assets/manifest.json +++ b/web/assets/manifest.json @@ -41,6 +41,21 @@ } }, "matcaps": { + "mol_chrome": { + "url": "gen/matcap_mol_chrome.webp" + }, + "mol_glass": { + "url": "gen/matcap_mol_glass.webp" + }, + "mol_matte": { + "url": "gen/matcap_mol_matte.webp" + }, + "mol_molten": { + "url": "gen/matcap_mol_molten.webp" + }, + "mol_pearl": { + "url": "gen/matcap_mol_pearl.webp" + }, "tissue_wet": { "url": "gen/matcap_tissue_wet.webp" } diff --git a/web/dev/laneA_molecules.html b/web/dev/laneA_molecules.html index d0fc768..b1103ea 100644 --- a/web/dev/laneA_molecules.html +++ b/web/dev/laneA_molecules.html @@ -32,10 +32,11 @@