Skill v1.0.0
currentLLM-judged scan90/100version: "1.0.0" name: dream-shader-optimize description: Clean up a machine-written DreamShader source — a decompiled or migrated .dss (or a 1.x .dsm / .dsf export) — into one a person would have written, without changing what it builds, and prove the graph is unchanged. Use when asked to optimize, clean up, tidy, refactor or make readable a source produced by the DreamShader decompiler or by dsc migrate.
dream-shader-optimize <file>
A decompiled or migrated source builds the right graph, but it reads like a machine wrote it. This skill turns it into the file a person would have written — and proves, node for node, that the graph did not change.
Paths below are relative to the plugin root, Plugins/DreamShader/.
The rule
Behaviour must not change. Renaming a local, inlining a value read once, naming a value read twice, moving repeated logic into a helper, swapping a class-exact node call for the HLSL that builds the same node — safe. Reordering lerp arguments, folding pow(x, 2) into x * x, dropping a saturate, renaming a parameter — not: those are changes, and they belong in a separate, explicitly requested edit.
Do this
1 — Read the file, its header warnings and the language. Every // Warning: DSHnnnn: … line under // Decompiled by DreamShader from … names something the text cannot state the way the asset has it; they are the work list (`Docs/diagnostics/DSH9xxx.md`). Read `reference/dss.md`.
A `.dsm` / `.dsf` from `-Format Legacy`? Do not clean 1.x text by hand. Decompile the asset again as 2.0 (the default) — or dsc migrate the file (`dream-shader-migrate`) — and optimize the .dss. The 2.0 text starts far cleaner: a value read once is already inline, one read twice already has one local, literals are exact.
2 — Capture the graph before touching anything. dump-graph generates the source in memory and writes a canonical JSON of each asset's graph — node classes, properties, connections, pin order; no coordinates, no GUIDs. It writes no asset:
pwsh -File Plugins/DreamShader/.skill/dsc.ps1 dump-graph DShader/Decompiled/Materials/Game/M_Steel.dss -Out Saved/Optimize/before
If an asset of that path already exists on disk, dump-graph reads it as it stands instead of generating it (the run says so). A fresh decompile under Decompiled/ has none.
3 — Apply the passes below. Then check it.
4 — Prove nothing changed.
pwsh -File Plugins/DreamShader/.skill/dsc.ps1 dump-graph DShader/Decompiled/Materials/Game/M_Steel.dss -Out Saved/Optimize/aftergit diff --no-index -- Saved/Optimize/before Saved/Optimize/after
Every hunk is a behavioural difference. The only acceptable ones are those the user asked for, and the Constant nodes a -Readable decompile adds (the shader is the same). Anything else: undo that pass.
5 — Retarget, last. Move the file to the place that names the original asset, or give its export a /// @name /Game/Materials/Metal/M_Steel — the step that makes this source authoritative. Then compile -Force -CleanNew once; delete nothing until the rebuilt asset has been looked at in the editor.
6 — Report what changed, the dump diff verdict, and — separately — every header warning you could not resolve from the source alone. That list is for the human; it needs the original asset open.
The passes
Resolve the header warnings
Each // Warning: is something the asset has and the text could not say. Some are fixed in the source (a function asset that moved: correct the /// @asset path); some only in the original asset (re-assign it, decompile again); some are accepted with the user (a node property the language cannot state). Delete a warning line only once it is resolved or accepted.
Rename what deserves a name
Locals named after their node (Multiply_2, Sample, Combined) become what the value is: scanline, bandPhase, edgeFalloff.
- A renamed local must be renamed in its
#pragma layout(Node, Var = …)line too, or that position is orphaned. - A `uniform`'s name is the parameter's name. Renaming it renames the parameter, and every material instance
that overrides it silently loses the override. Keep the name — or rename with /// @name <the old name> above it, which keeps the parameter.
- An
exportfunction's name is the asset's name: renaming it moves the asset unless/// @namekeeps it.
Prefer HLSL to class-exact node calls
UE.Multiply(A = x, B = y) is x * y; UE.LinearInterpolate(A = a, B = b, Alpha = t) is lerp(a, b, t). Write the HLSL where it builds the same node, and keep UE.<Class>(…) where the language has no spelling for it. Or decompile again with -Readable, which does this throughout (the graph may gain Constant nodes; the shader does not change).
Factor repeated logic
Logic written out twice becomes a helper (float3 Tint(float3 C, float K) { … }, no linkage keyword). A helper is inlined at each call, and two identical calls are one node, so the graph does not grow.
Keep the layout
#pragma layout(…) lines are the node positions of the original graph and #pragma region its comment boxes. They change no behaviour, and without them a large graph comes back auto-laid-out. Keep them.
Leave the numbers alone
The 2.0 decompiler writes the shortest decimal that reads back as the same float, so 6.2831855 is the value the asset holds. "Restoring" 6.2831853 changes nothing; rounding to 6.28 changes the material.
A 1.x export, if the user insists on keeping it
The 1.x exporter (-Format Legacy) is a migration starting point, not a guarantee. What to know about its text:
Settingsalways starts withDomain,ShadingModelandBlendMode, whether or not they were set.Backendis
never written, so the file rebuilds on the project default — add Backend = "Graph"; if the graph must stay a plain material.
ShadingModel = "Substrate"is forced when aBase.FrontMaterialwas decompiled. Do not "correct" it.- Struct-, array-, map- and set-valued node properties are dropped silently. A
MaterialFunctionCallwith no
function becomes 0.0, and a graph cycle a default literal — both with a warning.
- Literals are rounded to six decimals (
6.2831853→6.283185): restore recognisable constants. DS_Shared_Nnames are DreamShader's own reroute nodes, carried over from a graph DreamShader built.ParameterName="…"means the identifier had to differ from the parameter: renaming the identifier is safe,
deleting ParameterName is not.
- It rebuilds into a second asset under
Decompiled/…untilShader(Name=…)is retargeted.
The full table of what each decompiler reproduces: `Docs/tools/decompiler.md`.
Gotchas
- Re-running the decompiler overwrites the cleaned file. Move it out of
Decompiled/once you own it. - `@sort` is the material instance's parameter order. Reordering the
uniformlines is free; changing a
@sort value changes what an artist sees.
- A material instance decompiles to a `.dsi` of overrides only — there is little to optimize, and its
uniform names must match the parent's exactly.
- `dump-graph` boots the editor too. Capture before, apply every pass, capture after — two runs, not one per
pass.
See also
- `dream-shader-decompile` — producing the input
- `dream-shader-verify` —
checkandcompile - `Docs/tools/commandlet.md` —
dump-graphand diffing two captures