{"id":46311,"plugin_id":"plugin_asdk_app_6acd235a70a48191812b3ba9c471b4ac","kind":"skill","collection_source":"plugin_package","comparison_source":null,"observed_at":"2026-10-24T13:29:11.581Z","digest":"baba14a2947b8ebd54672c97f061b4264ae7db1aa8e5ad588e276632ef9599d9","against":null,"payload":{"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"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}