← SplineCONTENT HISTORY
WHAT CHANGED
Update to Spline
Snapshot Oct 24, 2026 · 13:29 UTC · version 1.2.0
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Spline and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Scene performance optimization: reading analyze_scene reports, reducing polygons, decimate(), merge(), bakeBoolean(), consolidating materials, taming lights/shadows/effects/textures, removing unused content — the measure→fix→verify workflow and visual-parity rules. Load for \"optimize my scene\", \"reduce the file size\", \"make it run faster\", \"improve FPS\", \"slow/laggy\", \"too many polygons\", and every fix requested from the Performance panel.",
"included_files": [],
"name": "spline-3d-scene-optimization",
"skill_md_contents": "---\nname: spline-3d-scene-optimization\ndescription: \"Scene performance optimization: reading analyze_scene reports, reducing polygons, decimate(), merge(), bakeBoolean(), consolidating materials, taming lights/shadows/effects/textures, removing unused content — the measure→fix→verify workflow and visual-parity rules. Load for \\\"optimize my scene\\\", \\\"reduce the file size\\\", \\\"make it run faster\\\", \\\"improve FPS\\\", \\\"slow/laggy\\\", \\\"too many polygons\\\", and every fix requested from the Performance panel.\"\n---\n\n# Scene optimization\n\nOptimizing means making the scene CHEAPER — fewer polygons, draw calls,\nshader layers, shadow maps and texture bytes — while it keeps LOOKING the\nsame. Both halves are the job: a fix that visibly changes the scene is a\nfailure, and so is \"optimizing\" things that were never the bottleneck.\n\n## What costs what (mental model)\n\n- POLYGONS — export size + memory + every render pass. Cost drivers:\n segment counts, subdivision levels (each level ≈ ×4 polygons), cloners\n (polygons × count), imported/generated meshes. Parametric shapes have\n segment dials; imported/generated/baked meshes don't, and `decimate` is\n the dial for those.\n- OBJECTS — draw calls (frame rate) + document size. Hundreds of small\n meshes cost more than one merged mesh with the same triangles.\n- MATERIALS & LAYERS — shader work per pixel. Every layer compiles into the\n object's shader; depth/noise/displace/fresnel stacks multiply quickly.\n Duplicated materials also defeat batching and bloat the file.\n- TEXTURES (image layers) — download bytes + GPU memory. Expensive to LOAD\n regardless of how small they render.\n- LIGHTS & SHADOWS — each shadow-casting light re-renders the scene into a\n shadow map. Light count and shadow resolution are frame-rate dials.\n- EFFECTS (postprocessing) — full-screen per-pixel cost; depth of field and\n bloom are the heavy ones.\n- LIVE BOOLEANS — CPU recompute whenever an operand changes; NESTED Booleans\n recompute in chains and slow loading.\n- AUDIO ASSETS — export bytes.\n\n## Workflow — measure, fix, verify (always in this order)\n\n1. MEASURE: call `analyze_scene`. It returns every metric with a\n good/average/bad mark and thresholds, plus the concrete offenders by id\n and name: the heaviest meshes (with polygon counts and clone\n multipliers), high-subdivision meshes, nested Booleans, all lights with\n shadow flags, layer-heavy materials, duplicated material assets, and\n unused-asset counts. Do NOT guess at problems the report doesn't show.\n2. SNAPSHOT THE LOOK: `set_view` on the scene's content + `take_screenshot`\n once, BEFORE touching anything — this is the parity reference.\n3. PLAN by impact: fix the 'bad' marks first, then 'average'. The usual\n impact order: heavy meshes / subdivisions / cloner counts →\n `decimate` for the ones with no segment dials → merge candidates →\n nested Booleans → materials & layers → lights & shadows →\n textures & effects → unused content. Tell the user the plan in one short\n paragraph before editing.\n4. FIX with the recipes below — one category per `run_code` call, so a\n failure stays isolated and undo stays meaningful.\n5. VERIFY: re-run `analyze_scene` and compare numbers; `take_screenshot`\n from the SAME view and compare against the reference. Finish by\n reporting the before → after numbers for what you changed (e.g.\n \"polygons 2.1M → 340k, lights 6 → 3\") — never claim an improvement you\n did not re-measure.\n\n## Safety rules (hard)\n\n- VISUAL PARITY: the scene must look the same afterwards. If a screenshot\n shows a visible change (popped silhouette, lost detail, changed shading),\n undo-fix it: restore the parameter toward its original value until parity\n returns.\n- DESTRUCTIVE fixes — `decimate`, `merge`, `bakeBoolean`, deleting objects —\n cannot be re-edited the way the originals could. Use them on static dressing\n freely when asked to optimize; when a target has states, events, or\n animations (check `get_scene`'s flags / `get_objects`), SKIP it and tell\n the user why.\n- NEVER delete a visible object to hit a number. Deleting is only for\n objects that are invisible, fully buried inside others, or explicitly\n approved by the user.\n- Do not \"optimize\" what is already marked good — leave healthy metrics\n alone.\n\n## Recipes\n\n### Heavy meshes — reduce segments\n`analyze_scene` lists the heaviest meshes with their polygon counts. Select\neach by id and lower its tessellation with `geometry(...)`:\n```\nselect(o => o.id === '<id from the report>');\ngeometry({ widthSegments: 24, heightSegments: 16 }); // Sphere/Plane\n```\n- `radialSegments` (Cylinder / Cone / Pyramid), `radialSegments` +\n `tubularSegments` (Torus), `detail` (Dodecahedron / Icosahedron),\n `segments`/`verticalSegments` (Lathe), `cornerSegments` (rounded corners).\n- Halving a segment count roughly quarters a lat/long shape's polygons; a\n 24-segment sphere is visually identical to a 64-segment one at most sizes.\n- For procedural meshes (terrain/ocean/tree/rock), lower their resolution/\n detail props instead (`get_objects` shows the current values).\n\n### Meshes with no segments to lower — `decimate`\nImported models, AI-generated meshes, baked booleans, merged dressing and\nsculpted subdivs have no tessellation params: their polygons live in vertex\ndata. `decimate(target, ratio)` reduces that vertex data directly. The\ntarget LEADS and is required — the edit is destructive, so it never inherits\nthe selection — and `ratio` is the fraction of the TRIANGLES to keep, so a\n70k-triangle mesh at 0.5 comes back at 35k:\n```\ndecimate('Statue', 0.5); // half the triangles\ndecimate(['Rock A', 'Rock B'], 0.25);\ndecimate(get('selection'), 0.5); // what the user has selected\ndecimate('all', 0.5); // every mesh in the scene\n```\n- It preserves the LOOK, not just the shape: uvs, textures, materials,\n normals and vertex colors survive intact — surviving vertices keep their\n original values and nothing is interpolated, so a textured model comes back\n textured. uv seams and hard edges shorten along themselves with every\n corner moving together, so islands stay watertight; material borders are\n pinned. Multi-material meshes keep their per-triangle material assignment.\n- It is a READ alias: the result reports `from`/`to` vertex and triangle\n counts per mesh. Report the reduction from THOSE numbers.\n- DESTRUCTIVE: the mesh becomes a plain non-parametric mesh. A decimated\n Sphere can't have its `widthSegments` raised again and a decimated subdiv\n has no cage left — so on a PARAMETRIC shape prefer the segment dials\n above, and use `decimate` where they don't exist.\n- Same TWO-CALL RULE as `boolean`: the mesh must exist before the\n `run_code` call that decimates it.\n- Screenshot after: 0.5 is usually invisible, 0.25 is safe on organic\n shapes, below ~0.1 silhouettes start to facet. Step down and check rather\n than jumping straight to a big cut.\n- `reachedTarget: false` is NOT a failure to retry — it means that mesh's\n floor has been reached, and `stoppedBecause` says which of three floors:\n the shape budget (the next collapse would have moved the surface further\n than ~1% of the mesh's size, so it stopped instead of flattening it),\n vertices pinned on material borders or non-manifold junctions, or the mesh\n running out of legal collapses because its uv islands are already at their\n minimum triangle count. Report the reduction it DID get and move on;\n only pass `{ errorLimit: 0.05 }` to push past the shape budget if the\n user explicitly wants a smaller mesh at the cost of the silhouette, and\n screenshot the result. Options ride in a third argument:\n `decimate('Statue', 0.3, { errorLimit: 0.05 })`.\n- Do NOT tell the user a mesh needs retopology or texture rebaking in an\n external tool because `decimate` stopped short. Report the floor the\n `stoppedBecause` field actually names, and what you already achieved.\n- It reduces up to 2M triangles per mesh (a generated Spline model arrives at\n 375k, so that is 5.3x headroom) and runs OFF the main thread, so a dense\n mesh takes seconds without freezing the editor. Past 2M it refuses: split\n the mesh and reduce the parts, or re-import it lighter.\n- Live Booleans (`bakeBoolean` first), Shape Blends (lower\n `resolutionLevel`) and text (its own segment dials) can't be decimated;\n the error says which to use.\n\n### High subdivisions\nThe report's `highSubdivisionMeshes` are SubdivGeometry meshes at level 2+.\nEach level is ≈ ×4 polygons — drop one level at a time and re-check the look:\n```\nselect(o => o.id === '<id>');\ngeometry({ subdivisions: 1 });\n```\n\n### Cloner counts\nCloned polygons = source polygons × count, so a heavy CLONE row in the\nreport has two dials: reduce the SOURCE mesh's segments (above), or lower\nthe cloner's count with a partial `clone(...)` edit on the selected cloner\n(`get_objects` shows its current type and settings):\n```\nselect(o => o.id === '<cloner id>');\nclone({ grid: { count: { x: 6, y: 1, z: 6 } } });\n```\nLowering the source's polygons is usually the better first move — it keeps\nthe composition identical.\n\n### Too many objects — merge static dressing\n`merge(target?)` collapses meshes into ONE editable mesh (fewer draw calls\nand objects; same triangles). The extra meshes are DELETED and the result\nuses a single material (the LAST target's), so only merge STATIC,\nSAME-MATERIAL dressing — scattered rocks, a fence run, greeble. Targets\nmust exist before the call (the `boolean` two-call rule):\n```\nmerge(['Rock 1', 'Rock 2', 'Rock 3']);\nrename('Rocks (merged)');\n```\n- Do NOT merge: objects with events/states/animations (they die with the\n merge), objects that need separate materials, or anything the user still\n edits individually. Do NOT merge cloners — reduce their counts instead.\n\n### Booleans — bake what no longer needs to be live\nLive Booleans recompute on every operand change; NESTED ones (the report's\n`nestedBooleans`) also chain at load time. `bakeBoolean` applies the CSG\nresult in place (\"Apply & Edit\"): the Boolean becomes a plain mesh, its\noperand children are deleted, nothing recomputes again:\n```\nbakeBoolean('Wall with windows');\n```\n- Bake innermost nested Booleans first, then the outer ones if needed.\n- The cut becomes permanent — skip Booleans whose operands are animated\n (an animated hole needs to stay live) and say so.\n- Keep operand segment counts modest BEFORE baking: baked polygons are\n forever.\n\n### Materials & layers\n- Duplicated assets: the report lists them; `listMaterials()` shows usage.\n Repoint objects at one shared asset with `applyMaterial('Name')`, then\n delete the leftovers with `deleteMaterial('Name 2')`.\n- Layer-heavy materials: every layer is shader cost. The report's\n `layerTypeHotspots` name the abused layer TYPE and the owning material\n (objectId / assetId). For an object's material: select the object, then\n `removeLayer(idOrType)` — a TYPE string removes the top layer of that\n type (`removeLayer('noise')`), an id removes that exact layer. For shared\n assets, `getMaterial(nameOrId)` lists the layers with ids and\n `updateMaterial('Name', { layers: [...] })` rewrites them. The usual fat:\n stacked noise/depth layers doing one gradient's job, displace layers on\n meshes too coarse to show them, duplicate color layers at full opacity.\n Screenshot after layer removals — layers are look-defining.\n- Many one-off materials on similar objects → converge them onto one shared\n asset (`createMaterial` from the best one, `applyMaterial` on the rest).\n\n### Textures\nImage layers cost load time regardless of display size. Where a texture is\ndecorative (soft gradient, grain, simple pattern), swap it for a\ncolor/gradient/noise LAYER — same look, zero download. Textures that must\nstay (logos, photos, baked art) are fine — just don't stack several on one\nmaterial.\n\n### Lights & shadows\nThe report lists every light with its shadow flag. Frame cost scales with\nshadow-CASTING lights:\n```\nselect(o => o.id === '<fill light id>');\nupdateLight({ shadows: false });\n```\n- One shadow-casting key light is enough for most scenes; fills and rims\n rarely need shadows.\n- Delete lights that visibly change nothing (verify with a screenshot).\n- `shadowResolution` above 1024 is rarely visible — lower it on lights that\n have it raised.\n- For glow, emissive materials beat extra point lights.\n\n### Effects\n`fx('depthOfField', 'none')` kills the heaviest effect; bloom is next.\nOnly touch effects the user can spare — they're look-defining. Suggest\nrather than remove when unsure.\n\n### Hidden & unused content\n- `select(o => !o.visible)` finds hidden objects; `remove(...)` the ones\n that are clearly abandoned (verify names/content first — hidden ≠ junk;\n states/events may toggle visibility at runtime, so check\n `get_objects` before deleting anything hidden).\n- Unused material assets: `deleteMaterial('Name')` per asset. Unused\n image/color/audio assets can't be deleted from the DSL — point the user\n at the Performance panel's \"Remove Unused Assets\" buttons for those.\n\n## What you cannot do (be honest about it)\n\n- Export byte size and image/audio compression live in the export pipeline —\n `analyze_scene` doesn't measure bytes, and exports already compress and\n strip unused assets automatically. Don't promise byte numbers; report the\n metric improvements you measured.\n- You can't change export settings or the user's renderer; the Performance\n panel's \"Optimization mode\" toggle is the user's own preview of the scene\n with shadows/AO/effects reduced — you don't control it.\n\n## Reporting\n\nEnd with a compact before → after summary of the metrics you improved, what\neach fix was, and anything you deliberately skipped (with the reason —\n\"skipped merging the doors: they carry open/close events\"). If the report\nstill has bad marks you could not fix safely, say what the user could do\nmanually.\n"
}SHA-256 of public snapshot: baba14a2947b8ebd54672c97f061b4264ae7db1aa8e5ad588e276632ef9599d9