Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions .claude/skills/project-export/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ XnaFiddle **always** runs the user's code in the browser via KNI's **BlazorGL**

The export dialog has a single **runtime** selector (`_exportRuntime`: KNI / MonoGame / FNA); the platform checkboxes are *within* that family. So a multi-platform export is always one family — **KNI and MonoGame can never be combined**, and FNA is single-target. This is enforced in the UI (`SetExportRuntime`, the runtime radios), so `ExportMultiPlatform`'s shared common project only ever sees one family. The exporter itself hard-blocks only the FNA-mixing case (`Export(targets,…)` throws); the KNI/MonoGame split is a UI guarantee. Design consequence: per-target logic never has to reconcile two framework families in one solution.

## MonoGame 3.8.5 (preview) — policy and the `.Native` convention
## MonoGame the `.Native` shared-reference convention

**Policy — do not relitigate, do not warn.** MonoGame 3.8.5 is in preview but near shipping. Treat it as a first-class, supported target — NOT risky/experimental to be avoided or hedged. **Never warn the user that 3.8.5 is "preview."** We actively track and support it so we are ready the moment stable drops. When work touches MonoGame, prefer adopting 3.8.5's conventions.
MonoGame (currently 3.8.5, GA) is a first-class export target: `Directory.Build.props` has a single `MonoGameFrameworkVersion`, and `WindowsDX12`/`DesktopVK` (the DX12/Vulkan `MonoGame.Framework.Native` + `MonoGame.Runtime.*` backends) are regular, always-available targets like DesktopGL/WindowsDX/Android — no version gating, no preview/stable split anywhere in the codebase.

**`.Native` is the renderer-agnostic compile reference for shared libraries.** `MonoGame.Framework.Native` is a backend-agnostic *managed* framework assembly — no graphics backend baked in — that functions like a reference assembly / `netstandard` lowest-common-denominator. The 3.8.5 `mg2dstartkit` template references it from the shared `.Core` library with `PrivateAssets=All`; each platform head then supplies the concrete backend (`MonoGame.Framework.DesktopGL` / `WindowsDX` / etc. for classic backends; `MonoGame.Framework.Native` + native `MonoGame.Runtime.*.{DX12,Vulkan}` for the new backends). Compiling the shared lib against `.Native` prevents leaking renderer-specific API. Going forward, exported shared/common projects should follow this convention.
**`.Native` is the renderer-agnostic compile reference for shared libraries.** `MonoGame.Framework.Native` is a backend-agnostic *managed* framework assembly — no graphics backend baked in — that functions like a reference assembly / `netstandard` lowest-common-denominator (the convention MonoGame's own `mg2dstartkit` template uses). `GenerateCommonCsproj` in `ProjectExporter.cs` unconditionally references `MonoGame.Framework.Native` with `PrivateAssets=All` for the shared/common project whenever any MonoGame target is present; each platform head then supplies its concrete backend (`MonoGame.Framework.DesktopGL`/`WindowsDX`/`Android` for classic backends; `MonoGame.Framework.Native` + native `MonoGame.Runtime.*.{DX12,Vulkan}` for the new backends). Compiling the shared lib against `.Native` prevents leaking renderer-specific API.

**Two independent layers — do not conflate:**
- *Managed reference* — `.Native` is the agnostic contract; ANY backend's `MonoGame.Framework.dll` satisfies it at runtime. Classic and native backends are interchangeable at this layer (the template compiles `.Core` against `.Native` yet ships all-classic heads).
- *Compiled content* — effects / `.xnb` do NOT cross between the classic MGCB pipeline (GL/DX) and the new native Content Builder (DX12/Vulkan). This is the real incompatibility (e.g. Apos.Shapes / Gum shaders failing on DX12/VK) and it is orthogonal to the reference choice.

**Exporter note:** `ProjectExporter.cs` currently hardwires `MonoGame.Framework.DesktopGL` as the shared-project compile reference (~line 411, the `isMonoGame` branch). The convention-correct reference is `MonoGame.Framework.Native`.
- *Managed reference* — `.Native` is the agnostic contract; ANY backend's `MonoGame.Framework.dll` satisfies it at runtime. Classic and native backends are interchangeable at this layer.
- *Compiled content* — effects / `.xnb` do NOT cross between the classic MGCB pipeline (GL/DX) and the new native Content Builder (DX12/Vulkan). This is the real incompatibility (e.g. Apos.Shapes / Gum shaders failing on DX12/VK) and it is orthogonal to the reference choice. See `BuildPackageList`'s `MonoGameWindowsDX12`/`MonoGameDesktopVK` branch.

## The core design contract

Expand All @@ -50,11 +48,22 @@ Third-party library packages are added per-target by scanning the user's source

Exports honor the contract above for shaders by shipping the **`.fx` source** (into `Content/`) plus a **ShadowDusk `PackageReference`**, and recompiling at runtime — no XNB, no MGCB. `Export` takes a `shaders` (`name.fx -> HLSL`) map. The seam: the shared/common project references **`ShadowDusk.Core`** (the `IShaderCompiler` interface, net8.0, no natives) and the generated content manager has an `Effect` branch that compiles against it; each **per-platform** project references the concrete compiler (`ShadowDusk.Compiler` desktop+FNA / `ShadowDusk.Wasm` Blazor) and its entry point injects it + the `PlatformTarget` (GL vs DX vs `Fna` is just that value; FNA emits legacy D3D9 `.fxb` instead of `.mgfx`). `ProjectExporter.SupportsRuntimeShaders(target)` is the single source of truth for which targets are wired (desktop GL/DX + Blazor + FNA Desktop); Android/iOS and MonoGame DX12/VK are gated (ship `.fx`, no compiler) — issue #52. Full detail lives in the **`shaders`** skill.

### Opt-in MGCB shader mode (`ShaderCompileMode.ContentPipeline`)
### Two orthogonal axes: `ContentBuildMode` and `ShaderCompileMode` (issue #52 follow-up)

`Export` takes two independent enums instead of one conflated choice:

- **`ContentBuildMode`** — how non-shader `Content/` assets get built: `Raw` (ship as-is, the default), `ClassicMgcb` (legacy `dotnet-mgcb`; `.png`/`.wav` compile to `.xnb` via `TextureImporter`/`WavImporter`, classic MonoGame targets only — `IsMonoGameClassic`), or `ContentBuilder` (MonoGame 3.8.5 GA's code-first Content Builder, wired for **every** MonoGame target including DX12/Vulkan since it isn't limited to a fixed platform list).
- **`ShaderCompileMode`** — how `.fx` gets compiled: `ShadowDusk` (default; ship source, recompile at runtime — same as the in-browser editor) or `Native` (compile at build time via whichever pipeline `ContentBuildMode` selected). **`Native` is a per-content-strategy opt-out from ShadowDusk, not a third content strategy** — ShadowDusk is the default shader compiler under all three `ContentBuildMode` values, including `ClassicMgcb`/`ContentBuilder`. Picking a content strategy alone does not force shaders through its native compiler.

Resolution predicates (`ProjectExporter.cs`): `UsesClassicMgcbAssets`/`UsesContentBuilder` resolve axis A per-target; `UsesClassicMgcbShaders`/`UsesContentBuilderShaders` resolve axis B *against* axis A (`Native` is a no-op unless the matching content strategy is also selected for that target); `UsesNativeShaders` is their union; `UsesShadowDuskShaders` is "supported target and Native wasn't actually selected here" (so e.g. `Native`+`ClassicMgcb` on `MonoGameDesktopVK`, which has no classic MGCB, falls through to ShadowDusk). `CompilesShippedShaders(target, shaderMode, contentMode)` is the combined "will shaders load here at all" predicate the dialog uses for the gated-platform message. `SupportsContentBuilder(target)` gates offering the Content Builder radio (every MonoGame target).

**Asset routing** (`GenerateCsproj`/`Export`): `PipelineAssetImporters` lists the only two extensions with a real stock importer (`.png`→`TextureImporter`/`TextureProcessor` with `ColorKeyEnabled=False` forced — MonoGame's default color-keys pure magenta to transparent, which would silently punch holes in a shipped `.png`; `.wav`→`WavImporter`/`SoundEffectProcessor`). `NonPipelineAssetExtensions` (`.achx`, `.fnt`, `.ttf`, `.ember`, `.xnb`, tilemap/level/text-data formats) always ships raw regardless of `ContentBuildMode` — no importer exists for them, and `.xnb` is already-compiled output. Under `ContentBuilder`, pipeline-eligible assets route into the separate `{projectName}.Content/Assets/` folder (picked up by its `WildcardRule("*")`); everything else, including a `ShadowDusk`-mode `.fx`, stays in the head's own `Content/`. Under `ClassicMgcb`, `.png`/`.wav` stay physically in `Content/` (same as `Raw`) — only the csproj's copy-to-output `<None>` exclusion changes for them, since `Content.mgcb` compiles them in place.

MGCB path mechanics (`GenerateContentMgcb`): emits one `Content.mgcb` per export covering whichever of shaders/assets route through it — an `EffectImporter`/`EffectProcessor` block per `.fx` when `useClassicMgcbShaders`, plus an importer/processor block per `PipelineAssetImporters`-matched asset independently (so a `ClassicMgcb` export with `ShadowDusk` shaders still gets a valid `.mgcb` covering just its `.png`/`.wav`). `MonoGame.Content.Builder.Task` overrides `/platform` (and out/intermediate dirs) per project from `$(MonoGamePlatform)` at build time, so the file's `/platform` line is just a default. `/profile` is **not** overridden — set to `HiDef` to match how the editor runs shaders. The existing MGCB infra (`NeedsMgcbToolManifest` = `IsMonoGameClassic`, the dotnet-mgcb manifest, the Mark-of-the-Web unblock) is reused unchanged.

`Export` takes a `shaderCompileMode` (default `ShadowDusk`). `ContentPipeline` routes user `.fx` through the **classic MonoGame Content Pipeline** (build-time `.xnb`) instead of ShadowDusk — for a canonical, ShadowDusk-free MonoGame project. It is honored **only on classic MonoGame targets** (`IsMonoGameClassic`: DesktopGL/WindowsDX/Android); every other target ignores it and stays ShadowDusk. Per-target strategy resolves to ShadowDusk / Mgcb / Gated via `UsesShadowDuskShaders` / `UsesMgcbShaders`; `CompilesShippedShaders(target, mode)` is the mode-aware "will shaders load here" predicate the dialog uses for the gated-platform message.
**Export dialog UI** (`Index.razor`/`.cs`): two independent radio groups — "Content strategy:" (Raw / Classic MGCB / Content Builder) always shown for MonoGame, and "Shader compiler:" (ShadowDusk / Native) shown only when content strategy ≠ `Raw`. `EffectiveContentModes()` collapses back to `(ShadowDusk, Raw)` when the runtime isn't MonoGame.

MGCB path mechanics: emit a `Content.mgcb` (EffectImporter/EffectProcessor per `.fx`) + a `<MonoGameContentReference>`, drop the `.fx`/`.mgcb` from the raw `<None>` copy (Exclude on Include forms, `<None Remove>` on the single-platform Update form), and **suppress** all ShadowDusk wiring for that head (no package, no entry-point injection, no `Effect` branch — `Content.Load<Effect>` falls through `RawContentManager` to the stock loader reading the `.xnb`). One **shared** `Content.mgcb` serves every head: `MonoGame.Content.Builder.Task` overrides `/platform` (and out/intermediate dirs) per project from `$(MonoGamePlatform)` at build time, so the file's `/platform` line is just a default. `/profile` is **not** overridden — set to `HiDef` to match how the editor runs shaders. The existing MGCB infra (`NeedsMgcbToolManifest` = `IsMonoGameClassic`, the dotnet-mgcb manifest, the Mark-of-the-Web unblock) is reused unchanged.
**MonoGame DX12 gate:** `MonoGameWindowsDX12` shaders stay gated under ShadowDusk (no DX12 backend there) — only `ContentBuildMode.ContentBuilder` + `ShaderCompileMode.Native` closes that gap today. `MonoGameDesktopVK` has a ShadowDusk Vulkan backend, so it isn't gated under the default `ShadowDusk` mode.

## Library MGCB content compiles into the NuGet cache at build time (not shipped)

Expand All @@ -64,7 +73,7 @@ Verified by inspecting `.nupkg` entries, `Content.mgcb`, and file timestamps.
- At the **consuming project's build**, `MonoGame.Content.Builder.Task` runs MGCB and compiles `apos-shapes.fx` → `.xnb` (EffectImporter/EffectProcessor). `Content.mgcb`'s `/outputDir:bin/$(Platform)` + `/intermediateDir:obj/$(Platform)` are relative to the **`.mgcb`'s own location**, so MGCB writes the output AND its incremental cache **inside the package cache**: `~/.nuget/packages/<lib>/<ver>/buildTransitive/Content/{bin,obj}/<Platform>/…`. The `.xnb` is then copied into the consuming project's output `Content/`.
- So a compiled `.xnb` **does** live in the NuGet cache — as **build output, not shipped content**. To distinguish shipped vs. built: list the immutable `.nupkg` entries (not the extracted folder) and cross-check timestamps (built `.xnb` is newer than the extracted source and differs per platform/build).
- **Clean-rebuild gotcha:** deleting the consuming project's `bin`/`obj` does **not** force a content recompile — MGCB's incremental cache lives in the **package folder**, so it reuses the cached `.xnb`. To simulate a fresh user, delete `…/buildTransitive/Content/{bin,obj}` (or the whole `<lib>/<ver>/` package folder, which re-extracts source only). A genuinely fresh user has only the `.fx`, so their first build always compiles it.
- This MGCB path exists only for **classic** targets (DesktopGL/WindowsDX, GL/DX). DX12/Vulkan use the separate native Content Builder — why library effects (Apos.Shapes/Gum) don't build/load there. See the *compiled content* layer in the 3.8.5 section.
- This MGCB path exists only for **classic** targets (DesktopGL/WindowsDX, GL/DX). DX12/Vulkan use the separate native Content Builder — why library effects (Apos.Shapes/Gum) don't build/load there. See the *compiled content* layer in the MonoGame `.Native` section.

## Not yet documented (grow only on confusion)

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/shaders/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ User-facing pattern: `Content.Load<Effect>("Name")` in `LoadContent`, optionally
| **Share URL** (`#code=...&shaders=...`) | Yes — `_shareShaders` JSON in a `&shaders=` fragment |
| **Snippet** (`#snippet=...`) | Yes — `SnippetModel.Shaders` (`List<ShaderFile>`). Not consumed by `SnippetExpander`; the page applies them separately via `ApplyShaderFilesAsync` |
| **Gist** | Partly — the copy-to-clipboard puts **only the C#**; the UI warns the user to add each `.fx` as a separate gist file (same filename). Gist **import** reads every `.fx` file back into tabs |
| **Export** | Yes (issue #39) — the `.fx` **source** ships in the export `Content/` and the exported project recompiles it at runtime via ShadowDusk (no XNB/MGCB), exactly like the editor. `ProjectExporter.Export` takes a `shaders` (`name.fx -> HLSL`) map; the export call site re-collects via `CollectShaderFilesAsync()`. Wired for desktop GL/DX (`ShadowDusk.Compiler`, `PlatformTarget.OpenGL`/`DirectX`), Blazor (`ShadowDusk.Wasm`, awaits `InitializeAsync` before the render loop), FNA (`ShadowDusk.Compiler`, `PlatformTarget.Fna` → legacy D3D9 `.fxb`, loaded by FNA's MojoShader `Effect` ctor — issue #54), and Android (`ShadowDusk.Compiler` with on-device native backends — issue #88). iOS and MonoGame DX12/VK are **gated** (ship `.fx`, no compiler wired) — issue #52. **Opt-in alternative:** `Export`'s `shaderCompileMode` (default `ShadowDusk`) can be `ContentPipeline` to compile user `.fx` via classic **MGCB** at build time (`.xnb`) instead — honored only on classic MonoGame targets, producing a canonical ShadowDusk-free project. See the `project-export` skill. |
| **Export** | Yes (issue #39) — the `.fx` **source** ships in the export `Content/` (or the Content Builder's `Assets/`, see below) and, by default, the exported project recompiles it at runtime via ShadowDusk, exactly like the editor. `ProjectExporter.Export` takes a `shaders` (`name.fx -> HLSL`) map; the export call site re-collects via `CollectShaderFilesAsync()`. Wired for desktop GL/DX (`ShadowDusk.Compiler`, `PlatformTarget.OpenGL`/`DirectX`), Blazor (`ShadowDusk.Wasm`, awaits `InitializeAsync` before the render loop), FNA (`ShadowDusk.Compiler`, `PlatformTarget.Fna` → legacy D3D9 `.fxb`, loaded by FNA's MojoShader `Effect` ctor — issue #54), Android (`ShadowDusk.Compiler` with on-device native backends — issue #88), and MonoGame DesktopVK (ShadowDusk 0.12.0+ Vulkan backend). iOS and MonoGame DX12 are **gated** under ShadowDusk (ship `.fx`, no compiler wired) — issue #52. **Opt-in alternative:** `Export`'s `shaderCompileMode` (default `ShadowDusk`) can be `Native` (renamed from `ContentPipeline`) to compile user `.fx` at build time via whichever `ContentBuildMode` content strategy is selected (classic MGCB's `EffectProcessor`, or the Content Builder's own pipeline) instead — `Native` under `ContentBuilder` is also the only way to close the MonoGame DX12 gate. See the `project-export` skill for the full two-axis (`ContentBuildMode` x `ShaderCompileMode`) model. |

`ShaderFile` (`ShaderFile.cs`) is the model: `Name` (filename incl. `.fx`) + `Source` (HLSL). `CollectShaderFilesAsync` snapshots open tabs into `_shareShaders` (refreshed on share/snippet build because shader edits don't fire the C#-only content callback). `ApplyShaderFilesAsync` clears stale tabs then re-opens the payload's shaders.

Expand Down
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ XnaFiddle is a standalone KNI game runner with an in-browser C# editor. It is a

On **any** task — research, maintenance, new features, bug fixes, refactors, docs, reviews — first check whether an available skill applies, and load it before proceeding. Skills (e.g. `file-loading`, `intellisense`, `verify`, `run`, `code-review`) carry condensed, repo-specific knowledge that prevents rediscovering subsystem details from scratch. When a skill matches the work, invoke it; if none fit, say so briefly and continue. This check is in addition to, not a replacement for, the Agent Workflow below.

**Proactively suggest a new or updated skill** when — and only when — you finish work having re-derived reusable subsystem knowledge that no skill covers, or having found an existing skill stale/wrong. Don't wait to be asked; surface it as a suggestion and let the user decide (don't create one unprompted). But the bar is **high**: every skill line costs tokens on every relevant load, so err toward *not* proposing. Only suggest when you're confident the knowledge will be re-hit by a concrete upcoming task, not for one-off facts the code already records or that this conversation alone needed. When unsure, stay silent.
**Finding an existing skill stale or wrong** → fix it directly, no suggestion or confirmation needed. This is correcting an inaccuracy, not a new-content judgment call.

**Proactively suggest a new skill** when — and only when — you finish work having re-derived reusable subsystem knowledge that no skill covers. Don't wait to be asked; surface it as a suggestion and let the user decide (don't create one unprompted). But the bar is **high**: every skill line costs tokens on every relevant load, so err toward *not* proposing. Only suggest when you're confident the knowledge will be re-hit by a concrete upcoming task, not for one-off facts the code already records or that this conversation alone needed. When unsure, stay silent.

## Agent Workflow

Expand Down
Loading