diff --git a/packages/chess/src/ui/LayoutPicker.tsx b/packages/chess/src/ui/LayoutPicker.tsx
index fe2a41c..eb38740 100644
--- a/packages/chess/src/ui/LayoutPicker.tsx
+++ b/packages/chess/src/ui/LayoutPicker.tsx
@@ -144,7 +144,14 @@ export function LayoutPicker({
value.suggestedPresets.length > 0 && (
Suggested rules:
- {value.suggestedPresets.map((presetId) => {
+ {value.suggestedPresets
+ // Never surface engine-internal plumbing presets
+ // (__x__ naming convention) even if a layout
+ // accidentally lists one — they auto-activate via
+ // the engine constructor and can't be meaningfully
+ // toggled by the user.
+ .filter((id) => !(id.startsWith('__') && id.endsWith('__')))
+ .map((presetId) => {
const presetDef = PRESET_REGISTRY.get(presetId);
const label = presetDef?.name ?? presetId;
const isActive = activations.some(a => a.id === presetId);
diff --git a/packages/chess/src/ui/PresetPanel.filter.test.ts b/packages/chess/src/ui/PresetPanel.filter.test.ts
new file mode 100644
index 0000000..59c43d5
--- /dev/null
+++ b/packages/chess/src/ui/PresetPanel.filter.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect } from "vitest";
+import { PRESET_REGISTRY } from "../presets/registry.js";
+import "../presets/index.js";
+// Side-effect import: registers the __modifier-profile-integration__
+// pseudo-preset (top-level register call in apply.ts). The modifier
+// barrel doesn't pull this in, so we import directly to mirror the
+// engine constructor's boot ordering.
+import "../modifiers/apply.js";
+
+/**
+ * Regression guard — the Modifier Profile editor's Presets tab used
+ * to surface the engine-internal `__modifier-profile-integration__`
+ * preset because it calls PRESET_REGISTRY.getAll() directly. That
+ * preset is plumbing (it wires modifier facts into move-gen / damage
+ * pipelines) and is auto-activated by the ChessEngine constructor,
+ * so it should never appear as something a user can toggle on a
+ * profile.
+ *
+ * The panel now filters any id matching the `__x__` double-underscore
+ * convention. This test pins that contract.
+ */
+describe("Preset authoring surface excludes internal presets", () => {
+ const isInternal = (id: string) => id.startsWith("__") && id.endsWith("__");
+
+ it("registry contains the modifier-profile-integration internal preset", () => {
+ const all = PRESET_REGISTRY.getAll();
+ const internal = all.filter((p) => isInternal(p.id));
+ expect(internal.length).toBeGreaterThan(0);
+ expect(internal.map((p) => p.id)).toContain(
+ "__modifier-profile-integration__",
+ );
+ });
+
+ it("filtering internal presets yields a user-authoring-safe list", () => {
+ const all = PRESET_REGISTRY.getAll();
+ const userFacing = all.filter((p) => !isInternal(p.id));
+ expect(userFacing.length).toBeGreaterThan(0);
+ for (const p of userFacing) {
+ expect(isInternal(p.id)).toBe(false);
+ }
+ // Sanity: at least one well-known user-facing preset survives
+ // the filter. extinction-chess is a stable long-standing id so
+ // this assertion won't churn with preset-registry shuffling.
+ expect(userFacing.map((p) => p.id)).toContain("extinction-chess");
+ });
+});
diff --git a/packages/chess/src/ui/PresetPanel.tsx b/packages/chess/src/ui/PresetPanel.tsx
index a3f3291..fc3a89e 100644
--- a/packages/chess/src/ui/PresetPanel.tsx
+++ b/packages/chess/src/ui/PresetPanel.tsx
@@ -121,7 +121,23 @@ export function PresetPanel({
onChange,
boundLayout,
}: PresetPanelProps) {
- const allPresets = useMemo(() => PRESET_REGISTRY.getAll(), []);
+ // Filter engine-internal presets out of the authoring surface.
+ // Convention: any id wrapped in double-underscores (`__x__`) is
+ // reserved for plumbing presets the engine auto-activates — e.g.
+ // `__modifier-profile-integration__` wires modifier facts into
+ // move-gen / damage pipelines. Those should never appear in a
+ // profile's bundled activations because they can't be meaningfully
+ // toggled; they either flip on via the engine constructor or they
+ // don't. Same guard protects against any future internal presets
+ // that follow the naming convention without our having to
+ // hand-maintain an exclusion list.
+ const allPresets = useMemo(
+ () =>
+ PRESET_REGISTRY.getAll().filter(
+ (p) => !(p.id.startsWith('__') && p.id.endsWith('__')),
+ ),
+ [],
+ );
const activeById = useMemo(() => {
const m = new Map();
for (const a of activations) m.set(a.id, a);