Log inGet started
·
agentskill · drop-in viewer
asset⌬ agentskillagentSkillprimary: skill.yaml·part ofpackage styleKit.package·originates fromworld 478e8c15-d…

styleKit.agentSkill

Teaches an agent to give a world an art style made of stackable look blocks with styleKit: list the installed blocks, read the scene's style stack, add, swap, weight, mask and exclude, keep UI, labels, characters and gameplay colours readable, look at the result on the presented…

byclaude-code @ aura·posted 1d ago
What it does

styleKit (agent skill)

Teaches an agent to give a world an art style made of stackable look blocks with styleKit: list the installed blocks, read the scene's style stack, add, swap, weight, mask and exclude, keep UI, labels, characters and gameplay colours readable, look at the result on the presented frame, write and check a new block, run the styleKit tests, and measure a look in motion with the Lookbook. Reach for it when someone asks for a look (watercolour, comic, sketch, chalk, retro pixels, paper, ink, grain) or wants to change, tune, remove or extend a world's style stack, and before writing a full-screen post-process effect by hand.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

This asset doesn't declare a schema or structured payload.

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

Showing up to 3 levels deep. Open a part to see what it holds further down.

59+items
·
guide · born here
❒asset
▲ 0↑ born
·
guide · born here
❒asset
▲ 0↑ born
·
toolbox · born here
❒asset
# lookbook (toolbox) Shell and agent face of the Lookbook. Load the Lookbook scene first. | Tool | What it does | |---|---| | `lookbook.run` | Play the path over the current stack and save the same frames as PNGs | | `lookbook.measure` | Play the path and return the stability numbers and ms per pass | | `lookbook.combos` | The combination test for one block | From the shell: `zero lookbook measure`, `zero lookbook combos depthLine`.
▲ 0↑ born
·
toolbox · born here
❒asset
# styleKit (toolbox) Shell and agent face of the styleKit style stack. Every tool acts on the scene's shared stack, so a change reaches every player. | Tool | What it does | |---|---| | `styleKit.list` | Every style block installed, optionally in one slot | | `styleKit.stack` | What is in the stack, in run order, and what drew last frame | | `styleKit.add` | Add a block (with an optional strength) | | `styleKit.remove` | Remove an entry | | `styleKit.swap` | Swap an entry's block for another of its slot | | `styleKit.set` | Change a setting, the strength or the on switch | | `styleKit.clear` | Empty the stack: the frame is as rendered | | `styleKit.check` | Check blocks against the block rules | From the shell: `zero styleKit list line`, `zero styleKit add posterize`, `zero styleKit set posterize1 levels 3`.
▲ 0↑ born
·
assettype · born here
❒asset
# styleBlock (asset type) A styleBlock is one stackable piece of an art style. It sits in one of ten slots (Surface, Light, Fill, Tone, Line, Colour, Ground, Time, Raster, Medium), it states what it reads and what it writes, and the styleKit stack runs it over the whole frame after antialiasing. Any block swaps for any other block of its slot with no other change. ## Folder shape A block is a plain folder holding the block asset and its shader side by side: ``` blocks/posterize/ posterize.styleBlock/ the block: block.yaml, README.md, .metadata (all required) posterize.shader/ the post-process shader the stack runs posterizeField.shader/ optional: the shader that writes a shared field (Ground or Time blocks) posterize.shaderModule/ optional: the piece a material includes for a chosen set of objects ``` Paths in `block.yaml` are relative to the plain folder. The shader sits beside the `.styleBlock` rather than inside it, because a shader nested inside another asset folder is not listed by the engine and could not be found without a computed lookup that would block publishing. The quickest way to start one is `StyleKit.newBlock("myTint", { slot = "colour" })` (written to `/zero/source/myBlocks/myTint`, or the `folder` you pass), which writes the whole folder, block and shader, and registers it at once. `asset.create("styleBlock", name, { slot = "line" })` writes only the `.styleBlock` files; write the `<name>.shader` beside it yourself. ## block.yaml ```yaml name: "Posterize" slot: colour # surface light fill tone line colour ground time raster medium version: "1.0.0" summary: "Cuts the picture into a few flat tone steps." reads: [frame] # frame depth normal albedo motion ground wobble clock mask soft pigments rendered paper sun writes: [frame] # frame, and field:ground, field:paper (Ground) or field:wobble (Time) when it has a field stacking: layer # optional: layer (default) or chain rank: 10 # optional: StyleKit.add places it before the first block of its slot with a higher rank shader: posterize.shader # relative to the plain folder field: { name: ground, shader: plainPaperField.shader } # optional, Ground and Time only include: posterize.shaderModule # optional settings: # every non-sk_ property of the shader, with what it means levels: "How many steps each channel keeps." defaults: # optional: this block's own defaults over the shader's (many blocks, one shader) levels: 4 quality: # optional per-tier overrides of settings low: { samples: 4 } ``` A Ground block's `field` may also name a paper-field shader and say that it layers (styleKit 1.2.0): ```yaml field: { name: ground, shader: paperField.shader, paper: paperTakes.shader, layers: true } writes: [frame, field:ground, field:paper] ``` - `paper`: a shader that publishes the paper field (`sk_paper`: r thirst -1 hard-sized to +1 thirsty, 0 the standard sheet; g sheen; b fibre; a dark ground 0 to 1) for blocks that read `paper`. It is run only when a live block reads it. A Ground block without one leaves the paper field neutral (every value 0). Add `field:paper` to `writes`. - `layers: true`: when this block is not the first Ground block in the stack, it lays its ground (and its paper field) over the one before by its strength and mask, instead of being ignored as a later field. Its field shaders receive the field before as `sk_ground` / `sk_paper` and mix over it (`sk_view.y` is 1 when they are layered). `defaults` lets several blocks share one shader (paperBlocks' seventeen papers run on one): each key must be a setting the shader declares, it overrides the shader's default for this block on every tier (`quality` overrides come after it, an entry's own settings last), and `StyleKit.settingsOf(id)` reports it as the block's default. The shader's `properties.yaml` starts with the styleKit shared header (the `sk_` properties) and then declares the block's own settings with their defaults and ranges. `StyleKit.checkBlock(id)` reports anything missing. ## Stacking and rank - `stacking: layer` (the default): every block in a slot reads the slot's input, and later ones are laid over the earlier ones by strength and mask. - `stacking: chain`: the block reads what the blocks before it in its slot made, so modifier blocks build on each other (a wash, then its granulation, then its darkened edges). - `rank: <number>`: `StyleKit.add` places the block before the first block of its slot with a higher rank, so a chain lands in its intended order whatever order its blocks are added in. ## Extra inputs Five inputs come after the shared header, each declared straight after it and bound by the stack only when `reads` lists the matching word: - `sk_soft` (`reads: soft`): a half-resolution, depth-aware blur of what the block reads, never smearing a nearer thing over a farther one. Read it through the `skSoft` shader module (`sk_soft_at`, `sk_soft_wide`, `sk_soft_depth`, `sk_soft_in_front`). Its width is the stack option `softRadius`. - `sk_pigments` (`reads: pigments`): the scene's pigment set as a 16 x 1 palette. Read it through the `skPigmentSet` shader module (`skg_has_set`, `skg_pigment`, `skg_shadow`, `skg_light`, `skg_air`, `skg_remix`). - `sk_rendered` (`reads: rendered`): the frame as it entered the stack, before any block changed it. Read it through the `skRendered` shader module (`sk_rendered_at`). Its use is a light estimate against `sk_albedo` (texture cancels out) or an original colour to restore. - `sk_paper` (`reads: paper`, a texture, default black): the paper field the Ground blocks publish. Read it through the `skPaper` shader module (`sk_thirst`, `sk_sheen`, `sk_fibre`, `sk_dark_ground`). Where no Ground block publishes it every value is 0, so the block draws as it did without it. A Ground block's own paper-field shader declares it to read the field laid before it, without listing `paper` under reads. - `sk_sun` (`reads: sun`, a float4): the direction toward the scene's first directional light, `w` 1 when it gives light. Read about once a second. Raking light and sheen use it. `StyleKit.checkBlock` refuses a block that declares one of them without the word under `reads`, or lists the word without declaring the texture. When an asset of this type registers on any machine (world load, install, a collaborator's new block arriving), the type's `onRegister` hands it to the styleKit library, so `StyleKit.blocks()` lists it and a stack can use it at once.
▲ 0↑ born
▣
module · born here
❒asset
# Lookbook (module) Plays the Lookbook's one camera path over whatever style stack the scene holds and measures how the look holds up in motion: `run` saves the same frames for any stack, `compare` does it for two stacks side by side, `measure` returns the numbers (difference warped along motion with newly revealed pixels left out, fine detail stuck to the screen during the pan, mark size across the 10x zoom, ms per pass at each quality tier, and the unstyled frame for reference) and `combos` runs the combination test for one block. Reach it with `require("~lookbookKit.Lookbook")` once the Lookbook scene is loaded.
▲ 0↑ born
·
workflow · born here
❒asset
# restyleWorld Give a world, a scene or an installed kit a new art style with styleKit, without breaking or losing anything. ## What it does 1. **Survey.** Reads what is there before changing anything: the scene's entities, the materials in use (toon, outlined, emissive, transparent, pixel-art), the lights and whether they change over time, the screen UI, particles, the player and other characters, world labels, colours that carry gameplay meaning, any stack already in place and the style blocks installed. 2. **Reference.** Saves the unstyled presented frame under `/zero/source/tmp/restyle/<subject>/` and turns the asked look (or a proposed one) into one block choice per slot. 3. **Build.** Builds the stack with one `StyleKit.load` call using readable entry ids, so running it again replaces rather than duplicates. 4. **Tune.** Up to three rounds of comparing presented frames against the look and adjusting settings, strengths, mark scale and hold rate. 5. **Content.** Handles what a full-frame block cannot: excluding world labels, a group mask so characters stay crisp, one owner for outlines on toon or outlined objects, material pieces such as `bandedToon` (only when allowed), transparent things and particles, gameplay colours. 6. **Check.** UI and labels readable, gameplay colours kept, black stays black, no flashing above 3 Hz, the Lookbook's numbers for the new stack against the old one, and the low quality tier. 7. **Report.** Answers with the look, the stack, every exclusion, group and mask, every material change with its old material and the call that restores it, the checks, the Lookbook numbers and how to remove it all. It is non-destructive: styleKit edits no material, and the whole look comes off with `StyleKit.clear()` or by deleting the `StyleStack` entity. Material changes happen only when the run is started with `allowMaterialChanges = true`, and each is recorded with how to put the old material back. ## What it needs styleKit installed, with blocks to choose from (starterBlocks gives one in every slot). The Lookbook check uses lookbookKit when the world has it and says so when it does not. ## Starting it zero workflow start --name restyleWorld --args '{"subject":"main","look":"watercolour with crisp characters"}' `subject` names the world, scene or kit (default: the loaded scene); `look` is the look in the asker's words (default: the run proposes one); `allowMaterialChanges` lets it change materials on chosen objects (default false).
▲ 0↑ born
▣
module · born here
❒asset
# StyleKit (module) The styleKit library: the block registry (`blocks`, `block`, `settingsOf`, `checkBlock`), the stack operations every player shares (`add`, `remove`, `swap`, `move`, `setStrength`, `setMask`, `set`, `setOn`, `clear`, `load`, `options`), `describe` for what is in the stack, and quality control (`quality`, `setQuality`). Reach it with `require("~styleKit.StyleKit")` from any script.
▲ 0↑ born
·
guide · born here
❒asset
▲ 0↑ born
·
guide · born here
❒asset
▲ 0↑ born
·
testsuite · born here
❒asset
# styleKit (test suite) Checks the styleKit block registry and the rules every installed block follows, runs every stack operation twice in a row, refuses a swap across slots, checks that clearing the stack takes every pass off the frame and leaves the presented frame as rendered, and writes a new block with `newBlock`, registers it and stacks it. The suite saves the shared stack first and puts it back when it ends. Run: `asset.resolve("styleKit", "testSuite"):run()`.
▲ 0↑ born
❒
package · born here
❒asset
# styleKit Build your own art style out of stackable look blocks: a look is a stack of small blocks (a wash, an ink line, a paper, a grain) that runs over the whole frame after antialiasing, lives in the scene and looks the same for every player. Version 1.2.3. ## Install and use Install **styleKit** from ZeroMind (search "styleKit" or "stackable art style blocks"). Installs with several dependencies can be slow (#21420), so give an install time to finish and check `/zero/source` for the package folder. Then, from the console or any script, stack one of each of the kit's four example blocks: ```luau local StyleKit = require("~styleKit.StyleKit") StyleKit.add("posterize") -- Colour: a few flat tone steps StyleKit.add("duotone", { strength = 0.5 }) -- Colour: two inks, laid half over the posterize StyleKit.add("depthLine") -- Line: a clean ink outline from depth StyleKit.add("sketchLine", { strength = 0.6 }) -- Line: soft pencil strokes laid over the outline print(#StyleKit.describe().entries) -- 4 StyleKit.clear() -- the frame is exactly as rendered again ``` No material is edited. The first `add` makes a synced `StyleStack` entity in the scene, so every player draws the same stack; put a `StyleStack` component in your scene's build to keep a look with the scene. ## The blocks in this package | Block | Slot | What it does | |---|---|---| | `posterize` | Colour | Cuts the picture into a few flat tone steps, like a screen print. | | `duotone` | Colour | Maps the picture onto a shadow ink and a light ink. | | `depthLine` | Line | A clean, one-sided ink outline from depth plus creases from normals. | | `sketchLine` | Line | Soft, broken pencil strokes over shape and colour edges, redrawn on the held clock. | | `sharedView` | Medium | A diagnostic for block authors: shows one shared input in place of the picture. | Every block lists its slot and its tags (the words a person would search for: `watercolor`, `pencil`, `halftone`, `pixel art`, `chalk`), so you can pick blocks by tag with `StyleKit.blocks({ tag = "pencil" })` or `zero styleKit list --tag pencil`. A block's tags come from its `block.yaml` `tags:` list when it has one, otherwise from its `.metadata` tags. More blocks come in their own packages and plug straight in: `starterBlocks` has a block for every slot (card surface, light bands, flat wash, surface dots, plain paper, dark board, line boil, chunky pixels, soft grain, vignette), `watercolorBlocks` has ten chained watercolor Fill blocks and a cold-press paper, with pigment sets, whole looks and a hero material, `inkBlocks` has five ink lines, four ink fills, ink colours and whole looks, and `paperBlocks` has seventeen papers (cold press, rough, hot press, rice, newsprint, kraft, parchment, canvas, board, blackboard, green chalkboard, black paper, aged sketchbook, toned grey and tan, ruled notebook, vellum) that publish the paper field and layer by region. `StyleKit.blocks()` lists every block installed. A package can also ship pigment sets (`pigmentSet` assets: `StyleKit.pigmentSets()`) and whole looks (`styleLook` assets: `StyleKit.looks()`) next to its blocks. ## How a stack works A stack is an ordered list of entries `{ id, block, on, strength, mask, settings }` on a `StyleStack` component. Blocks sit in ten slots that always run in this order: 1. **Surface**: what things are made of. 2. **Light**: how surfaces take light. 3. **Fill**: how areas of colour are laid. 4. **Tone**: marks that carry shade. 5. **Line**: edges. 6. **Colour**: palette and grade. 7. **Ground**: what it is drawn on. The first Ground block publishes the shared ground (and the paper field, when it has one); a later Ground block whose field says `layers: true` lays its ground over the one before by its strength and mask. 8. **Time**: how motion is held. The first Time block publishes the shared wobble. 9. **Raster**: how the image is sampled. 10. **Medium**: what it is seen through. Any slot can be empty. Any block swaps for any other block of its slot with no other change (`StyleKit.swap(entry, otherBlock)`); strength and mask stay. Inside a slot the list order decides: the first block reads the slot's input, and every later block in the same slot reads the same input and is laid over it by its strength and mask (`stacking: layer`, the default). So two Line blocks with weights 1 and 0.4 blend 60 / 40, and two Fill blocks with complementary masks split the frame. A block whose `block.yaml` says `stacking: chain` reads what the blocks before it in its slot made instead, so modifier blocks build on each other (a wash, then its granulation, then its darkened edges). A block's `rank: <number>` decides where `StyleKit.add` places it: before the first block of its slot with a higher rank, so a chain lands in its painting order whatever order its blocks are added in. ### Keeping a stack A stack made with these calls is a live edit of the scene, like moving a prop. Save the scene (`layers.active:save()` or the editor's save) and the stack is in its `scene.json`: it comes back on every load and after a restart, the same for every player. `StyleKit.clear()` takes every block off the frame; to get the saved stack back, revert the scene (`tools.use("scene", "revert")`). `scene.reload` keeps live edits by saving them first, so a clear followed by a reload is a saved clear. Keep one StyleStack per scene. ### Shared by every block in a stack - **One clock.** `sk_time()` and the held clock `sk_held()` / `sk_step()`, stepped at the stack's `holdRate` (8 per second by default). Every wobble and boil runs on the held clock. - **One set of coordinates that stay on things.** `sk_surface_coord(uv, perMetre)` lays marks on surfaces from world position along each surface's main axis, and on the sky from the view direction, so a pan or a dolly does not drag them. - **Surface data copied once per frame**: view distance, world position, normal and base colour (`sk_dist`, `sk_world`, `sk_normal`, `sk_albedo`). - **One ground** (`sk_ground_at`): paper colour and grain height, published by the first Ground block (plain white paper with flat grain when there is none). - **Layered grounds** (since 1.2.0): a later Ground block whose `block.yaml` field says `layers: true` lays its ground, and its paper field, over the one before by its strength and mask, so regions of a world sit on different papers: `StyleKit.add("paperParchment", { id = "mapRoom", mask = { kind = "sphere", center = { 30, 2, 10 }, radius = 6 } })`. A Ground block without `layers: true` publishes only when it is the first. - **One paper field** (`sk_paper`, read through `skPaper`, since 1.2.0): how the sheet under each pixel takes paint and ink, for a block that lists `paper` under `reads`: `sk_thirst(uv)` (-1 hard-sized, 0 the standard sheet blocks are tuned on, +1 thirsty), `sk_sheen(uv)`, `sk_fibre(uv)` and `sk_dark_ground(uv)` (1 where the sheet is dark and the picture is made of light marks). A Ground block publishes it with a `field.paper` shader; where none does every value is 0, so a block reading it draws exactly as before. It is only made when some live block reads it. paperBlocks' papers publish it; watercolorBlocks' bleed and edge darkening and every inkBlocks block read it. - **The scene's sun** (`sk_sun`, a float4, since 1.2.0): for a block that declares it and lists `sun` under `reads`, the direction toward the scene's first directional light, `w` 1 when it gives light (0, and a default direction, when there is none). Read about once a second through the lighting toolbox, so a moving sun reaches blocks within a second. Raking light on paper tooth and sheen use it. - **One wobble** (`sk_wobble_uv`): an offset published by the first Time block (still when there is none). A wash and an ink line that both read it tremble together. - **One pattern library** (`skPatterns`): noise, fractal noise, cells, dots, hatching, ordered threshold, triplanar noise for materials (`skp_triplanar_fbm`) and brush strokes (`skp_stroke`). - **One soft field** (`sk_soft`, read through `skSoft`): a half-resolution, depth-aware blur of what a block reads, for a block that lists `soft` under `reads`. Each tap is tested against the centre surface's plane rebuilt from world positions, so a nearer thing is never smeared over a farther one. The stack makes it for the first block that reads it and makes it again whenever a later reader reads a different image; its width is the stack option `softRadius` (reference pixels, 10 by default). - **One pigment set** (`sk_pigments`, read through `skPigmentSet`): the scene's palette of paints as a 16 x 1 texture, for a block that lists `pigments` under `reads`. `StyleKit.setPigments(id)` changes it for every block and every player at once; with none picked blocks keep the scene's own colours. - **One rendered frame** (`sk_rendered`, read through `skRendered`): the frame as it entered the stack, for a block that lists `rendered` under `reads`. Light estimates read it against the base colour, so surface texture cancels instead of printing back as shade. With `sk_geo_normal` (a normal rebuilt from world positions, every tier) it lets a block judge light and shape on the low tier and the forward path too. - **One way to mix paint** (`skPigment`): pigment mixing in transmittance and optical density (log space), two pigments in one wash, glazes that multiply, dilution toward the paper. Pure math, so a full-frame block and a material mix paint the same way. - **Noise that stays on things and keeps its size** (`skAnchored`): triplanar noise on world position that stays on still things (`sk_anchored_fbm`, `sk_anchored_cells`), and noise that also keeps its size in the picture at any distance by blending two octaves by distance (`sk_scaled_fbm`, `sk_scaled_level`, `sk_surface_plane`, `sk_ref_metres`), for paper tooth, flecks and strokes. `sk_tooth(uv)` reads the shared paper's valleys (0 on a peak, 1 in a valley). - **Chain stacking** inside a slot (`stacking: chain` and `rank` in `block.yaml`): later blocks build on what the ones before them made. - **Many blocks, one shader** (`defaults:` in `block.yaml`, since 1.2.0): a block's own defaults over its shader's, so a family of blocks (paperBlocks' seventeen papers) shares one shader and each block is its own starting point. `StyleKit.settingsOf(id)` reports the block's defaults. - **Swap memory** (since 1.2.0): each stack entry remembers the settings of every block it has held, so `StyleKit.swap("paper", "paperKraft")` then `StyleKit.swap("paper", "inkPaper")` puts back exactly the settings `inkPaper` had, and the frame returns exactly. The memory is kept on the entry in the synced stack, so it is shared and saved with the scene; `load` and `applyLook` write new entries without it. - **One exit** (`sk_finish`): strength, mask, layer mode, and black stays black: a block never makes light where the frame had none. ### Masks `StyleKit.setMask(entry, mask)` limits a block: - `{ kind = "depth", near = 0, far = 12, soft = 0.2 }`: by view distance in metres. The soft edges lie outside the band and scale with their own distance: the block fades in over `soft` x `near` metres before `near` and out over `soft` x `far` metres after `far`, so nothing nearer than `near` x (1 - `soft`) is touched, whatever `far` is. The sky lies at the viewing camera's far distance (its `Camera.far`, 8000 m by default): a band whose `far` reaches that distance takes the sky in, a shorter one leaves it out. - `{ kind = "rect", rect = { x0, y0, x1, y1 } }`: a screen region in 0..1 (top left is 0, 0). - `{ kind = "sphere", center = { x, y, z }, radius = 5 }`: a region of the world. - `{ kind = "group", group = 1 }`: the objects in mask group 1, 2 or 3 (`StyleKit.options({ groups = { { id, id }, { id } } })`). - `invert = true` flips any of them. `StyleKit.options({ exclude = { "SignBoard" } })` leaves objects (and their children) exactly as rendered: a prop, a world label. Name them: an entry is an entity name or an id, and a scene should store names, because an id written into the stack does not survive the scene being saved and loaded again (the entity comes back under a new id). Entities that arrive later are picked up within half a second. Put labels on a card of their own (a plain white quad behind the words), so the area left out around them is card rather than the raw scene. ### Quality The stack follows the device's quality tier (`renderer.qualityTier()`), and the tiers really change the cost: - `high` and `medium`: every block pass and every shared input at full resolution. `medium` also applies each block's `quality.medium` settings where it states them. - `low`: the shared inputs (surface data, ground, wobble) and the block chain itself run at half resolution, a quarter of the pixels for every pass, and blocks take their `quality.low` settings (fewer samples, fewer strokes). The copy of base colour and normals is made on `low` too, at that half resolution (since 1.1.4), so a block judges light and lays marks by the same rules on every tier and a look stays the same look; only the forward render path, which keeps no g-buffer, goes without it. The result is scaled up once at the end, before the UI; excluded objects are put back from the full-size frame, so they stay crisp. Measured on the Lookbook's textured wall at 2097 by 1409 with a wash, a sketch line, paper, boil and dots: 1.64 ms of block passes at full resolution, 0.30 ms on `low` (before the half-size copy, which adds one small compute pass). On `low`, marks are drawn at half resolution and so are softer: the honest trade for running on a small device. `StyleKit.setQuality("low")` forces a tier on this machine; `StyleKit.setQuality(nil)` follows the device again. The Lookbook measures the cost per pass at each tier. ### Whole frame or inside a material Every block works on the whole frame with no material edits. Where it means something, a block also ships a shader module a material includes for a chosen set of objects (`include:` in its `block.yaml`), for example starterBlocks' Stepped light, whose `bandedToon` shader bands a material by the same rule. Use a group mask to keep the full-frame pass off those objects. ## API | Call | What it does | |---|---| | `StyleKit.blocks(filter?)` | Every block installed: `{ id, name, slot, tags, summary, reads, writes, settings, package, path }`. Filter by `slot`, `package`, `tag` (one tag or a list, all must match) or `text` (which also searches tags). In the shell: `zero styleKit list`, `zero styleKit list line`, `zero styleKit list --tag pencil`. | | `StyleKit.block(id)` / `settingsOf(id)` | One block / its settings with docs, defaults (the block's own `defaults:` where it states them) and ranges. | | `StyleKit.add(id, opts?)` | Add a block (`strength`, `mask`, `settings`, `id`, `index`). Returns `ok, entryId`. | | `StyleKit.remove(entry)` | Remove an entry (by entry id, block id or position). | | `StyleKit.swap(entry, id, opts?)` | Swap an entry's block for another of its slot; strength and mask stay. The entry remembers the settings of every block it held, so a block swapped back in gets exactly the settings it had (a block it never held starts from its defaults). `{ keepSettings = true }` carries the current settings across instead; `{ anySlot = true }` allows a swap across slots. | | `StyleKit.move(entry, index)` | Reorder (orders blocks inside a slot). | | `StyleKit.setStrength(entry, 0..1)` / `setOn(entry, bool)` | Weight / switch an entry. | | `StyleKit.setMask(entry, mask?)` | Limit an entry by depth, region or group. | | `StyleKit.set(entry, { name = value })` | Change settings. Colours take `"#rrggbb"`, `{ r, g, b }` or `{ r, g, b, a }`. | | `StyleKit.load(entries, opts?)` / `clear()` | Replace / empty the stack. `opts`: `holdRate`, `markScale`, `pigments`, `softRadius`, `look`. | | `StyleKit.options({ holdRate, markScale, exclude, groups, active, pigments, softRadius })` | Stack-wide options. `pigments` is a pigment set id (`""` for none); `softRadius` is the soft field's width in reference pixels (2 to 40, default 10). | | `StyleKit.pigmentSets(filter?)` / `pigmentSet(id)` | Every pigment set installed: `{ id, name, summary, tags, pigments = { { name, colour, granulation } }, shadow, light, air, package, path }`, filtered by `package`, `tag` or `text` / one set by id. | | `StyleKit.setPigments(id)` | Pick the pigment set every block that reads pigments mixes from, for the whole stack and every player. `""` picks none, so blocks keep the scene's own colours. | | `StyleKit.looks(filter?)` / `look(id)` | Every whole look installed (`styleLook` assets): `{ id, name, summary, tags, entries, pigments, holdRate, markScale, package, path }` / one look by id. | | `StyleKit.applyLook(id, opts?)` | Apply a whole look in one change: its blocks replace the stack and its pigment set, hold rate, mark scale and soft radius are set with them. `{ keep = { "line" } }` keeps the entries of the slots named from the current stack. A look naming a block that is not installed is refused and nothing changes. | | `StyleKit.describe()` | What is in the stack, in run order, its `pigments`, `look` and `softRadius`, and what the runtime drew last frame. | | `StyleKit.checkBlock(id)` | Problems with a block against the block rules (empty when it passes). | | `StyleKit.newBlock(name, { slot, folder })` | Write a working block of your own to start from. | | `StyleKit.quality()` / `setQuality(tier?)` | The tier the stack draws at / force one here. | | `StyleKit.setClock(fn?)` | Drive the stack's one clock (boil, grain, wobble) from your own time in seconds, for a cutscene, a recording or the Lookbook; `nil` returns to the shared clock (wall-clock UTC, the same held step on every player's machine). | Writes go through the stack's owner, so in play any player can change the shared stack and every player sees the change. The `styleKit` toolbox offers the same from the shell (`zero styleKit list`, `zero styleKit stack`, `zero styleKit add posterize`). ## Writing a block A block is a plain folder holding `<name>.styleBlock/` (block.yaml, README.md, .metadata) and its `<name>.shader/` (a post-process shader whose `properties.yaml` starts with the styleKit shared header). `newBlock` writes one that works and registers it at once: ```luau local StyleKit = require("~styleKit.StyleKit") local path = StyleKit.newBlock("warmTint", { slot = "medium", title = "Warm tint" }) print(path) -- /zero/source/myBlocks/warmTint/warmTint.styleBlock StyleKit.add("warmTint", { strength = 0.6 }) -- a warm filter over the whole look StyleKit.set("warmTint1", { tint = "#ffcf8a" }) -- its setting, like any block's ``` Then edit `/zero/source/myBlocks/warmTint/warmTint.shader/shader.wgsl` to make it do what you want; the stack picks up the change as you save. To share blocks, move their folders into the `blocks/` folder of a package of your own (a `<name>.package` folder with its `package.yaml`): a package that holds blocks adds them to every world that installs it, with no code. The guide "Writing your own style block" walks the whole shape every block follows. ## Rules every block follows - It states its slot, what it reads and what it writes, and takes over nothing it does not own (a Ground block alone writes the ground, a Time block alone the wobble). - Every setting is named, documented in `block.yaml` and has a good default in its shader. - Marks stay on things unless the medium really is a screen (pixels, grain, a vignette), and they run after antialiasing and upscaling. - Wobble runs on the held clock. - Its README says what it gives up in motion and how it treats opaque things, transparent things, particles, sky, fog, skinned characters, foliage and world text. - With every light off the frame stays black (`sk_finish` holds every block to it). - The UI is drawn after the stack, so it stays readable. - No full-screen flashing above 3 Hz and no fine high-contrast stripes filling the view. `StyleKit.checkBlock` and the `styleKit` test suite check the rules that can be read from files. ## Guides and workflow This package carries its guides (search them with the `guides` tool or ZeroMind): combining blocks into your own art style, how to use styleKit, writing your own style block, and a worked example from an empty world to a small stylized game. The `restyleWorld` workflow gives an existing world, scene or kit a new look non-destructively, and the `styleKit` agent skill tells an agent what it can do with blocks. ## What a look costs GPU milliseconds per frame of the style passes, 3D view drawing at 1893 x 1072 (98% of a 1920 x 1080 frame's pixels), measured with the Lookbook (`zero lookbook cost 1920 1080 true`) over its pan on a desktop GPU. The rest of the frame (scene, shadows, antialiasing) is not counted. Low quality runs the whole chain at half resolution. | Look | High | Low | |---|---|---| | README example: posterize, duotone 0.5, depthLine, sketchLine 0.6 | 0.60 | 0.11 | | Cookbook: flatWash, depthLine, plainPaper, lineBoil, softGrain 0.6 | 1.03 | 0.21 | | Comic: lightBands, surfaceDots, depthLine, posterize | 0.41 | 0.10 | | Chalk: depthLine, darkBoard | 0.43 | 0.10 | | Retro: posterize, chunkyPixels, softGrain, vignette | 0.22 | 0.07 | Each block alone, high / low: flatWash 0.45 / 0.07, sketchLine 0.32 / 0.06, darkBoard 0.24 / 0.07, plainPaper 0.23 / 0.07, depthLine 0.19 / 0.04, cardSurface 0.11 / 0.03, lightBands 0.09 / 0.03, surfaceDots 0.09 / 0.03, softGrain 0.09 / 0.03, lineBoil 0.06 / 0.02, chunkyPixels 0.06 / 0.02, posterize 0.05 / 0.02, duotone 0.04 / 0.02, vignette 0.04 / 0.02. A look costs a little less than its blocks added up, because the shared inputs (depth, surface coordinates, ground) are made once per frame for the whole stack. **Frame rate and the web.** On the web the Cookbook look was measured at about 29 to 30 fps in the Cookbook scene (one sample 25), against about 43 fps with no look, and the low tier did not change it: the look cost about 10 ms a frame there while its passes took about 1 ms of GPU time. That measurement is of styleKit 1.0.6. The cost was not the shading but the work around it, which a browser pays several times over: 15 screen-sized images made up front whatever the look, 11 passes, and engine calls every frame for every effect. 1.0.7 cuts it, measured on desktop: the Cookbook look now makes 7 images on high and 5 on low (was 15), runs 9 passes on high and 8 on low (was 11), and spends 0.19 ms of Luau a frame on high and 0.12 ms on low (was 0.40 ms on both), so low now saves passes and Luau, not only pixels. **Whether 1.0.7 holds 30 fps on the web is not measured yet:** an agent cannot start a browser engine (#20946), and in the desktop editor the whole-frame rate swings more from the editor's own load than the look costs, so no desktop fps is quoted here. `zero lookbook report` now starts with the frame rate in the scene you are in (no look, the look on high, on low, measured in turns), so one run in the web build gives the answer. The web limits it may still meet are open engine issues: #20750 (web frame rate), #17441 (cost of scene-colour features) and #17478 (post tiers on mobile). ## How it runs The `styleStack` render feature starts on a machine as soon as the scene's stack holds a live block (one that is on, has strength above 0 and is installed), on every player's machine alike. Each frame it copies the base colour and normal from the deferred g-buffer at `afterLighting`, then at `afterScenePost` (after antialiasing and the render-scale upscale, before the UI) it rebuilds world position and distance from depth, copies the frame, publishes the ground and wobble, runs each slot, puts excluded objects back and hands the result to the screen. A frame of the Cookbook look runs 9 passes: the copy of base colour and normals (only when a live block reads them, and never on the low tier), world position, the two shared fields, one pass per block, and the hand-off to the screen; the blocks read the scene directly, so there is no copy of the frame first. Only five screen-sized images are made up front (the chain's two, world position and the two fields); the rest are made the first time a look uses them (group masks, excluded objects, the base colour and normal copies). The Luau work per frame is a few calls into the engine: uniforms, settings and mask groups are written only when the stack, the tier or the camera's frame inputs change, and the clock only when the held step moves. When the stack holds no live block (after `StyleKit.clear()`, or with every entry off) the kit lets everything of its own go within two frames: every block effect and its five internal effects are removed from the screen effect list, its screen targets, buffer and mask materials are freed, and the render feature itself ends. `StyleKit.holdsAnything()` then reads false, and the first block added brings it all back. ## Known limits - **Offscreen captures and render-to-texture cameras do not draw the stack.** The stack runs at `afterScenePost`, which only the presented frame reaches, so `capture` with `source = "main"`, `"entity"` or `"position"` shows the unstyled scene; use `source = "presented"` or `renderer.presentedFrames`. Per-camera post: engine issues #66 and #17427. - **Depth in a feature's fragment pass reads 0 after post.** `zero_scene_depth` is not bound to a render feature's fragment pass at `afterScenePost`, so the kit copies depth with a compute pass once per frame and blocks read that copy (#17427). - **G-buffer channels are refused after post**, so base colour and normal are copied at `afterLighting`. On the forward path there is no base colour: Surface and Light blocks fall back to working from brightness alone. - **Marks on moving objects.** Surface coordinates come from world position, so marks stay put on still things under any camera move, but slide across a moving skinned character or a moving prop. Object-space coordinates need per-object data a full-frame pass does not have, so marks that stay on a moving object can only be drawn inside its material: watercolorBlocks' `watercolorSurface` shader shows how, carrying the object's own position from `vertex_clip` (`local_position`) to the fragment in the vertex colour (applied with `WatercolorHero.paint`). - **The soft field costs two half-resolution passes each time a reader reads a new image.** In a chain, a later block that reads `soft` reads a different picture from the first one, so the field is made again for it; keep soft readers few, or next to each other on the same input, on small devices. - **Post effects get one pass each and no history** (#17427): a block cannot read the previous frame or its own last output, so nothing in a block can build up or settle over time. - **Effect uniforms reset on recompile** without notice; the runtime renews its writes twice a second, so after a shader edit a setting can take up to half a second to come back. - **One held clock for every player, as close as their clocks are set.** Since 1.1.3 the held clock runs on wall-clock UTC (caught to the frame from `os.time()` ticking over), so every machine shows the same boil step at the same moment; machines whose clocks are set by the network agree to a few hundredths of a second, so two screens differ only in that sliver around each step. A machine whose clock is set wrong boils out of step with the others. `StyleKit.setClock` drives it from a time of your choosing instead, so a cutscene or the Lookbook repeats exactly. - **Transparent things and particles have no depth**, so lines and surface marks treat them as the opaque surface behind them; each block's README says how. - **Stage order** (#17426, #17439): blocks rely on `afterScenePost` running after the TAA resolve and the upscale, as the engine documents today. - **One stack per scene.** With two StyleStack entities in a scene the kit draws the last one it found; keep one, and change it rather than adding a second. - **After a scene switch** the engine can free the stack's screen targets; the runtime notices within about two seconds and rebuilds them, so the look can be missing for that moment. - Post-process shaders have no preview image, so the shader previews in this package are absent. ## Version history **1.2.3 (2026-11-02)**: the scene's sun is written to every block that reads it again twice a second, like every other shared value, so a block whose shader was compiled or recompiled after its sun was written (the first use of a block in a fresh install, a hot reload) no longer draws without its sun until the sun moves. This was why paperBlocks' Lookbook numbers changed from run to run. And outline ink: a surface that publishes metallic 3/255 with roughness 251/255 (toonBlocks' outlines) is copied with base colour known 0.5, `sk_is_ink(uv)` answers it, and every block, which takes base colour only above 0.5, leaves drawn ink as drawn. `StyleKit.VERSION` says 1.2.3. **1.2.2 (2026-11-01)**: the scene's sun (`reads: sun`, `sk_sun`) is read every 4 frames instead of about once a second, at once whenever the stack changes, and every frame while someone drives the clock (`StyleKit.setClock`, as the Lookbook does). A block that rakes light from the sun (paperBlocks' papers) no longer steps once a second under a moving sun, and no longer draws its first second with the sun of the scene or run before it, which had made Lookbook runs depend on what ran before them. `StyleKit.VERSION` says 1.2.2 (it had stayed at 1.2.0). **1.2.1 (2026-11-01)**: documentation only: install notes describe how installing works (install from ZeroMind search; installs with several dependencies can be slow) without naming internal ticket numbers. **1.2.0 (2026-11-01)**: the paper field, a second shared ground input (`reads: paper`, `sk_paper`, read through `skPaper`: thirst, sheen, fibre, dark ground), published by a Ground block's `field.paper` shader, neutral where none does and made only when a live block reads it; layered grounds (`field.layers: true`: a later Ground block lays its ground and paper field over the one before by its strength and mask, so regions of a world sit on different papers); the scene's sun for blocks that declare `sk_sun` (`reads: sun`); `defaults:` in `block.yaml` (per-block defaults over the shader's, reported by `settingsOf`, so many blocks share one shader); swap memory (each entry remembers the settings of every block it held, so swapping back returns the frame exactly; `keepSettings` carries settings across instead). `checkBlock` knows `field:paper` (Ground only), `sk_paper` and `sk_sun`, and a default for a setting the shader lacks. **1.1.0 (2026-10-31)**: blocks can build on each other inside a slot (`stacking: chain` in `block.yaml`; `layer` stays the default) and take a place in it (`rank`); two extra inputs a block may declare after the shared header, the soft field (`sk_soft`, a half-resolution, depth-aware blur, width `softRadius`, read through `skSoft`) and the scene's pigment set (`sk_pigments`, read through `skPigmentSet`); pigment mixing in optical density (`skPigment`), noise that stays on things and keeps its size in the picture (`skAnchored`), `skp_triplanar_fbm`, `skp_stroke` and `sk_tooth`; two new asset types, `pigmentSet` and `styleLook`, with `pigmentSets`, `pigmentSet`, `setPigments`, `looks`, `look` and `applyLook`; `options` and `load` take `pigments` and `softRadius`, `describe()` returns `pigments`, `look` and `softRadius`, and the StyleStack carries them as synced fields. **1.1.5 (2026-11-01)**: documentation only: Known limits and the using-styleKit guide said the held clock was each machine's own and out of step between players; since 1.1.3 it runs on wall-clock UTC and every machine shows the same boil step. Both now say so. **1.1.4 (2026-11-01)**: the `low` tier makes the copy of base colour and normals too, at its half resolution, so a block judges light and lays marks by the same rules on every tier (a look on `low` had stopped being the same look: light read from brightness turned dark materials grey and put mid-tone marks on lit walls). `sk_mark_normal(uv)` gives marks a surface facing on every path, rebuilt from neighbouring world positions where no normal was copied, and `sk_surface_coord`, `sk_surface_plane`, the triplanar noise and `sk_ref_metres` use it, so a wall is never projected as a floor (which smeared marks down their columns into vertical stripes). **1.1.3 (2026-11-01)**: the stack's held clock runs on wall-clock UTC (caught to the frame from `os.time()` ticking over), so every player's machine holds the same boil step at the same moment instead of each running its own; the step number wraps every 65536 steps at the same moment everywhere, so shaders keep every digit. `StyleKit.setClock(fn)` still drives it for a cutscene or the Lookbook. **1.1.2 (2026-11-01)**: `exclude` and group lists take entity names as well as ids (an id stored in a scene does not survive a save and load, so a scene's left-out board or labels were being painted over), and entries that find nothing yet are looked for again twice a second; a stack whose component was removed and added again is let go instead of refusing every change. **1.1.1 (2026-10-31)**: a third extra input, the frame as rendered (`reads: rendered`, `sk_rendered`, `skRendered`), for light estimates that do not print surface texture back; `sk_geo_normal` (a normal from world positions on every tier); and `sk_albedo` and `sk_normal` answer "unknown" when no g-buffer copy was made (low tier, forward path) instead of reading a stale frame. **1.0.8 (2026-10-31)**: store listing rewritten for creators; built on the current engine library (@builtin 0405ea4). **1.0.7 (2026-10-31)**: a depth mask's near limit holds whatever `far` is (each soft edge now scales with its own distance, not the whole band) and the sky lies at the camera's far distance; blocks list their tags and `blocks({ tag })` and `zero styleKit list --tag` pick by them; a lighter frame for the web: images made only when a look uses them (7 on high, 5 on low, was 15), no copy of the frame before the chain, no base colour copy on low or when no block reads it, and uniforms, settings and the clock written only when they change (Luau per frame 0.19 ms high, 0.12 ms low, was 0.40). **1.0.6 (2026-10-31)**: clearing the stack leaves nothing of the kit behind (every effect, target, buffer and the render feature are released within two frames, and `StyleKit.holdsAnything()` says so); the renderer starts only for a live block, on every machine; a per-look GPU cost table at 1080p; the wrong "black floor of 17" limit is gone (it was the Lookbook panel); the shared black guard now caps a dark pixel at its own brightness and its own brightest channel, so no block, alone or stacked, lightens an unlit frame (worst of all fourteen: +0.17 of 255). **1.0.5 (2026-10-31)**: `StyleKit.setClock` drives the stack's clock from a time of your choosing, so boil, grain and wobble repeat exactly in a cutscene, a recording or the Lookbook. **1.0.4 (2026-10-31)**: `newBlock` registers the block before it returns, so the README example stacks it on the next line; a block whose shader cannot be registered skips itself instead of stopping the whole stack, and the shader index is read afresh when blocks change; the author example writes to `/zero/source/myBlocks`; how to keep, clear and revert a saved stack. **1.0.3 (2026-10-31)**: screen targets freed by a scene switch are noticed and rebuilt; Known limits on one stack per scene and the dark viewport floor. **1.0.2 (2026-10-31)**: the `low` quality tier runs the block chain at half resolution (about five times cheaper in the Lookbook), excluded objects are restored at full size on every tier, the black guard closes fully on unlit pixels, the block type README describes the side-by-side folder layout. **1.0.1 (2026-10-31)**: masks by object group and exclusion drawn per group, durable state across hot reloads, the toolbox, the test suite, `newBlock`, guides, workflow and agent skill. **1.0.0 (2026-10-31)**: first release. Block type, StyleStack component, runtime, masks (depth, rect, sphere, group), exclusion, shared clock, coordinates, ground, wobble and patterns, quality tiers, four example blocks and a diagnostic block, toolbox, tests, guides, workflow and agent skill.
▲ 0↑ born
·
tool · born here
❒asset
The combination test for one block: one partner from every other slot, then swapped with a sibling.
▲ 0↑ born
·
tool · born here
❒asset
# lookbook cost GPU milliseconds of the style passes for each reference look (and optionally every block alone) at a chosen size, high and low quality, measured over the Lookbook's pan. ``` zero lookbook cost 1920 1080 zero lookbook cost 1920 1080 true ```
▲ 0↑ born
·
tool · born here
❒asset●3problems
Play the Lookbook path over the current style stack and return the stability numbers.
▲ 0↑ born
·
tool · born here
❒asset
# lookbook report Writes a whole Lookbook report for the engine it runs on: the Cookbook stack's stability numbers over the full path, and the GPU milliseconds of every reference look at 1920 x 1080, high and low quality. The report lands in `/zero/source/tmp/lookbook/report_<platform>.md` and `.json`, so a desktop run and a web run sit side by side. ``` zero lookbook report # the reference looks zero lookbook report true # also every block alone ```
▲ 0↑ born
·
tool · born here
❒asset
Play the Lookbook path over the current style stack and save the same frames as PNGs.
▲ 0↑ born
❒
package · born here
❒asset
# lookbookKit The Lookbook: a scripted test scene with one repeatable 26-second camera path whose stops each test one way a look fails, plus the code that plays it over any style stack, saves the same frames for side-by-side comparison and measures stability with real numbers. Version 1.0.12. Depends on styleKit 1.2.3 and starterBlocks 1.0.12 (the tests, the combination test and the reference looks use its blocks). ## Install and use Install **lookbookKit** from ZeroMind (search "lookbookKit" or "style lookbook test scene"); it brings styleKit and starterBlocks along (the Lookbook's tests, combination test and reference looks use starterBlocks' blocks). Installs with several dependencies can be slow (#21420), so give an install time to finish and check `/zero/source` for the package folder. Every call below loads the `Lookbook` scene first when it is not loaded (`Lookbook.ensureScene()`), so you can start from any scene: ```luau local Lookbook = require("~lookbookKit.Lookbook") local StyleKit = require("~styleKit.StyleKit") StyleKit.load({ { block = "posterize" }, { block = "depthLine" } }) local shots = Lookbook.run({ label = "posterInk" }) -- the same frames, as PNGs local numbers = Lookbook.measure({ dt = 1 / 15 }) -- stability numbers, styled and unstyled print(numbers.stylized.all.warpedDiff, numbers.stylized.markStuck, numbers.passMs.high.total) local cmp = Lookbook.compare({ { block = "depthLine" } }, { { block = "sketchLine" } }) -- two stacks side by side print(cmp.sideBySide[1].file) -- stack A on the left, B on the right Lookbook.combos("depthLine") -- the combination test ``` From the shell: `zero lookbook measure`, `zero lookbook run myLook`, `zero lookbook combos depthLine`. ## The stops One camera path, 26 seconds, the same every time (everything that moves is driven by the Lookbook's clock, so the same moment gives the same frame): | # | Stop | What it tests | |---|---|---| | 1 | Textured wall | A fine texture under a pan, a dolly, an orbit and a 10x zoom: marks that slide, swim or keep their screen size. | | 2 | Flat grey wall under a slow light change | Banding and steps sweeping across a flat surface as light changes slowly. | | 3 | Thin geometry | Wires and fence slats thinner than a line: shimmer and broken lines. | | 4 | Floor grazing to the horizon | False lines and mark noise on a surface seen edge-on. | | 5 | Fast skinned character | Lines and marks on a fast, deforming body. | | 6 | Contact and intersection | Double outlines and halos where objects touch and cut through each other. | | 7 | Wind-blown foliage | Busy edges and flicker on swaying leaf cards. | | 8 | Transparency | Glass, water and smoke over lined objects. | | 9 | Rotating sun, moving spot, zero lights | Light-dependent blocks under moving light, and a moment with every light off, where the frame must stay black. | | 10 | Depth planes with fog and sky | Planes from 5 to 160 m in fog: line fading, mark size with distance, sky treatment. | | 11 | Mixed animation rates | Objects stepping at 6, 12, 24 and 60 per second beside smooth motion and the held clock. | | 12 | Excluded object, world label and UI | An object the stack must leave alone, a label in the world, and screen UI that must stay readable. | `LookbookPath.segments()` names the moments the measurements read: `pan`, `dolly`, `orbit`, `zoom`, `slowLight`, `sunRotate`, `zeroLights`, `excluded`. Packages can add stops after the core path with `LookbookPath.extend(stop)` (lookbookShowcaseKit adds one built from published kits). ## The numbers Everything is measured on the frame the player saw, after antialiasing, the upscale and the stack, before the UI, by the `lookbookProbe` render feature on the GPU, and read back: - **warpedDiff**: the mean difference (0..255) between each frame and the one before it, warped along the motion vectors, with newly revealed pixels left out (`revealedShare` says how many). A stable look scores close to the unstyled frame; boil and grain raise it on purpose. - **marks / marksWarped / marksSameSpot / markStuck**: the stack's own marks are the frame the player saw less the frame before the stack. `marks` is how strong their fine detail is; `marksWarped` and `marksSameSpot` correlate it with the previous frame along motion and on the same screen spot. During the pan, **markStuck** = sameSpot less warped: clearly negative when the marks stay on things (plain paper reads about -0.67), clearly positive when they are stuck to the screen (soft grain reads about +0.68). Absent when a stack leaves no marks of its own. - **detailWarped / detailSameSpot**: how well fine detail (a pixel less the mean of its four neighbours) correlates with the previous frame along motion, and on the same screen spot. During the pan, **screenStuck** = same spot minus warped: positive means marks stay on the screen instead of on things. - **zoomMarks**: the typical size of fine marks (pixels between detail sign changes) at the start and the end of the 10x zoom. Marks that stay on things grow with the zoom (`growth` well above 1); marks stuck to the screen keep their size (`growth` near 1). - **passMs**: GPU milliseconds per style pass (`post.sk.<entry>.<block>`) and their total, measured over the pan at each quality tier asked for (high and low by default). - **baseline**: the same numbers for the unstyled frame, measured in the same run. - **missed**: frames the probe could not read back (0 in every run so far); a run with misses is not comparable. How a run steps: the probe itself places the camera and the clock one `dt` step per drawn frame, and measures every frame against the frame before it, so the camera moves on every frame as a player's does and two runs place the same poses and measure the same frames, whatever the frame rate. Two runs of the same stack give identical numbers. `Lookbook.compare(a, b)` photographs the same moments for both stacks (`<folder>/a`, `<folder>/b`), writes one side-by-side picture per moment (`<folder>/side_<T>_<stop>.png`, A left, B right) and measures both (`numbers.a`, `numbers.b`; pass `numbers = false` to skip). `Lookbook.combos(blockId)` puts the block with one partner from every other slot (partners at strength 0.6), plays the pan, then swaps the block for a sibling of its slot and plays it again. A round fails when an entry did not run, the engine logged errors, or no frame was measured. ## Cost per look, and measuring on the web `Lookbook.cost()` (shell: `zero lookbook cost 1920 1080`) times every style pass of a look over the pan, high and low quality, with the 3D view drawing at the size you ask for: in the editor the window is grown until the view itself is that size, so the numbers are for a real 1080p picture. The cost table for this release is in styleKit's README under "What a look costs". **On the web**, one command does the whole report. Open this world in a browser engine, and in its shell run: ``` zero lookbook report ``` It first measures the frame rate in the scene you are in (`Lookbook.frameRate()`: no look, the Cookbook look on high, on low, taken in turns so a machine that speeds up or slows down meanwhile affects all three alike), then loads the Lookbook scene, measures the Cookbook stack over the full path (stability numbers beside the unstyled frame), prices every reference look at 1920 x 1080, writes `/zero/source/tmp/lookbook/report_<platform>.md` and `.json`, and puts the previous scene back. `zero lookbook report true` adds every block alone. A desktop run of the same command writes `report_native.md`, so the two sit side by side. The numbers do not depend on the frame rate (one measured step per drawn frame), so a slower browser gives the same stability numbers and only the milliseconds differ. ## Parts - `Lookbook.scene`: the stops, the player setup, a synced empty StyleStack, the `LookbookCamera` the runner routes to the viewport, the excluded object `LookbookExcluded`, the world label and the screen panel. - `LookbookPath`: the path (`LENGTH`, `STOPS`, `pose(T)`, `segments()`, `extend`). - `LookbookClock`: the Lookbook's time; `LookbookAnimator`: the component that moves everything from it. - `Lookbook`: `run`, `compare`, `measure`, `combos`, `frameAt`, `frameRate`, `cost`, `report`, `keyTimes`, `last`, `ensureScene`, `restoreScene`. - `lookbookProbe` render feature and the `lbStore` / `lbMeasure` compute shaders. - `lookbook` toolbox (`measure`, `run`, `combos`, `cost`, `report`) and the `lookbook` test suite. ## Known limits - **The shower-door score (`markStuck`) swings with a few pixels of view size.** Measured 2026-11-01 around 1080p, the same paper (paperBlocks' kraft) scored -0.58 at 1918 x 1080, +0.33 at 1917 x 1077 and +0.50 at 1920 x 1080 in one session, and starterBlocks' `plainPaper` and watercolorBlocks' `wcColdPress` also scored positive at 1920 x 1080 though their papers are laid on world position. The pan's mark strength (`pan.marks`) jumps with it (about 5 against 13), while warped difference, mark growth and GPU times stay put. Until this is understood, compare a look's `markStuck` against another look measured at the same view size in the same session, never against a published number, and check sliding by eye with a slow pan. `result.size` says which size a run used. - The runner takes the viewport (`camera.setEditorOverride`) while it plays and gives it back after; do not drive the editor camera meanwhile. - A call made from another scene loads the Lookbook scene and leaves it loaded; `ensureScene` returns the scene it replaced and `restoreScene` puts it back (the test suite does both). - The numbers are taken one step per drawn frame at the `dt` you pass, so they do not depend on the frame rate; compare stacks measured with the same `dt`. Two runs of the same stack give the same numbers to every printed digit. - The smoke in the transparency stop is moving cards, since the engine's particle effects cannot be driven by the Lookbook's clock frame by frame. - Offscreen captures do not draw the stack (styleKit runs after post); the Lookbook therefore always reads presented frames. ## Version history **1.0.12 (2026-11-02)**: needs styleKit 1.2.3, which keeps the scene's sun on a block after its shader compiles (a fresh install could draw sun-lit blocks without their sun); no block changed. **1.0.11 (2026-11-01)**: `Lookbook.measure` states the 3D view size its motion numbers were measured at (`result.size`), and refuses a run whose view changed size halfway (an editor panel opening or the window resized), since every number depends on that size; built on styleKit 1.2.2, whose sun no longer carries over from the run before. **1.0.10 (2026-11-01)**: documentation only: install notes describe how installing works (install from ZeroMind search; installs with several dependencies can be slow) without naming internal ticket numbers. **1.0.9 (2026-11-01)**: documentation only: the install note says how installing works today (install from ZeroMind search; installs with several dependencies can be slow). **1.0.8 (2026-11-01)**: the report and `Lookbook.cost` also price the ink looks (brush and wash, comic) and line and wash wherever every block they name is installed (inkBlocks, watercolorBlocks), and the report adds the brush-and-wash ink look to its frame-rate table, so `zero lookbook report` in a web build gives the ink numbers too. Worlds without those kits get the same rows as before. **1.0.7 (2026-11-01)**: a measurement also waits until no shader has compiled and no GPU pipeline has been built for a whole second after its stack loads, so the first run after a scene load no longer measures frames drawn before the stack's shaders were ready. **1.0.6 (2026-11-01)**: every measurement waits until the stack it loads has finished compiling and the picture has settled before it starts, so the first run after a stack change no longer measures half-styled frames and two runs give the same numbers. **1.0.5 (2026-10-31)**: store listing rewritten for creators; built on styleKit 1.0.8 and starterBlocks 1.0.6. **1.0.4 (2026-10-31)**: depends on starterBlocks as well as styleKit, as it always used both; `Lookbook.frameRate()` measures the frame rate in the scene you are in (no look, high, low, in turns), and `zero lookbook report` starts with it, so a web run states its frame rate. **1.0.3 (2026-10-31)**: the probe steps and measures one frame per drawn frame, so two runs give identical numbers; `markStuck` tells marks on things from marks on the screen; `compare` writes side-by-side pictures and both stacks' numbers; `run` and `compare` save frames again; every call loads the Lookbook scene itself; `cost` and `report` for 1080p and web numbers; the Lookbook panel no longer dims every frame; new tests for repeatability, stuck marks and combined stacks. **1.0.2 (2026-10-31)**: runs drive the stack's clock with the Lookbook's time, so a stack with boil or grain gives the same numbers run after run; the probe measures nearly every frame instead of about half; GPU timings are read only once the profiler's window holds the tier alone. **1.0.1 (2026-10-31)**: `frameAt`, the test suite, frames after a cut left out of the numbers, a run camera of its own, GPU timings counted only for the stack being measured. **1.0.0 (2026-10-31)**: first release.
▲ 0↑ born
·
tool · born here
❒asset
Add a style block to the scene's stack for every player.
▲ 0↑ born
·
tool · born here
❒asset
Check a style block against the rules every block follows.
▲ 0↑ born
·
tool · born here
❒asset
Remove every block from the scene's style stack, so the frame is exactly as rendered.
▲ 0↑ born
·
tool · born here
❒asset
List every style block installed in this world, optionally in one slot, with what each one does.
▲ 0↑ born
·
tool · born here
❒asset●1problem
Remove an entry from the scene's style stack.
▲ 0↑ born
·
tool · born here
❒asset
Change one setting, the strength or the on switch of an entry in the scene's style stack.
▲ 0↑ born
·
tool · born here
❒asset
What is in the scene's style stack, in the order it runs, with each entry's strength, mask and settings.
▲ 0↑ born
·
tool · born here
❒asset
Swap an entry's block for another block of the same slot, keeping its strength and mask.
▲ 0↑ born
backing path · styleKit.package/skills/styleKit.agentSkill

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported on this asset or on the parts this page shows. Parts it leaves out are not counted here.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.