The Two Skills
Both ship under the arcade-ecs: namespace so they don't collide with skills from other plugins. Claude auto-routes by description match — you describe what you want, the right skill fires.
arcade-ecs:ecs-api — API reference
Triggers when: Claude is writing or reviewing code that uses @babylonjsmarket/ecs or @babylonjsmarket/arcade. Also when you ask "how does X work" about a Component/System/World/EventBus method.
What it carries:
- The canonical public surface of every class — Component, Entity, System, World, EventBus, SceneLoader — derived from the source files at
packages/ecs/src/ECS/. Not training data. - An anti-pattern table of plausible-looking method names that don't exist but show up in generated code:
entity.getOrThrow(C)→ useentity.get(C)!entity.getComponent(C)/entity.hasComponent(C)→ useentity.get(C)/entity.has(C)world.eventBus→ useworld.getEventBus()(the field isprotected)onAttachOverride/onDetachOverrideon a System → those are Component hooks; Systems useonInitialize/onShutdownthis.entities.find(...)→this.entitiesis aSet, not an Array; useworld.getEntity(id)for O(1) lookup- A pointer to the source files of record so any specific claim can be re-verified.
Effect on generated code: the anti-patterns above are exactly what Claude tends to invent without the skill. With the skill loaded, they get caught before the first save.
arcade-ecs:scaffold-ecs — Component scaffolder
Triggers when: you describe a component concept — e.g. "add a Cooldown component", "create a Score system", "scaffold a Pickup mechanic."
What it produces: one TypeScript file at src/components/{Name}/{Name}.ts containing:
import { Component, System, EventBus, type ISystemQuery } from '@babylonjsmarket/ecs';
export const NameEvents = { /* output events */ } as const;
export const NameInputEvents = { /* listened events */ } as const;
export class NameComponent extends Component {
// pure data fields go here
}
export class NameSystem extends System {
query: ISystemQuery = { required: [NameComponent] };
private unsubscribes: Array<() => void> = [];
constructor(eventBus: EventBus) { super(eventBus); }
onInitialize() {
// subscribe; collect every returned unsubscribe
}
onShutdown() {
this.unsubscribes.forEach((u) => u());
this.unsubscribes = [];
}
onUpdate(_dt: number) {
for (const entity of this.entities) {
const c = entity.get(NameComponent)!;
// ...
}
}
}
A sibling {Name}.test.ts (Vitest skeleton against World + EventBus) is written too, if your project already has test files nearby. Projects without tests stay without tests.
Conventions baked in:
- Component is pure data — no
@babylonjs/*imports, no DOM, no side effects in the constructor. - System subscriptions go in
onInitializeand unsubscribes go inonShutdown. The unsubscribe-tracking pattern is non-negotiable; listeners leak otherwise. - Events are PascalCase consts pointing to lowercase dotted strings (
NameEvents.CHANGED = 'name.changed'). Never hand-write string literals at call sites.
When neither fires
The skills are scoped narrowly. If you're working on the website, on a CLI tool unrelated to the ECS, or on scene loading mechanics that don't fit the "add a Component" pattern, neither skill triggers. That's intentional — broad auto-invocation makes skills noisy.
To force-invoke explicitly, prefix your message with the namespaced name:
/arcade-ecs:scaffold-ecs add a Cooldown component
But in practice, describing what you want plainly is enough.
