diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000000..c1056df296 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -0,0 +1,12 @@ +{ + "name": "melonjs", + "description": "Skills for building games with melonJS, the open source 2D, 2.5D and 3D HTML5 game engine.", + "owner": { "name": "melonJS", "url": "https://melonjs.org" }, + "plugins": [ + { + "name": "melonjs", + "source": "./", + "description": "Build games with melonJS — 23 guides to the 2D, 2.5D and 3D HTML5 game engine, its conventions and idioms, so generated code runs the first time." + } + ] +} diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000000..afdba634ee --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,38 @@ +{ + "name": "melonjs", + "version": "20.3.0", + "description": "Build games with melonJS — 23 guides to the 2D, 2.5D and 3D HTML5 game engine, its conventions and idioms, so generated code runs the first time.", + "author": { + "name": "melonJS", + "url": "https://melonjs.org" + }, + "homepage": "https://melonjs.org", + "repository": "https://github.com/melonjs/melonJS", + "license": "MIT", + "keywords": ["melonjs", "game-engine", "html5", "webgpu", "webgl", "gamedev"], + "skills": [ + "./packages/melonjs/skills/melonjs", + "./packages/melonjs/skills/melonjs-getting-started", + "./packages/melonjs/skills/melonjs-20-migration", + "./packages/melonjs/skills/melonjs-renderables", + "./packages/melonjs/skills/melonjs-3d", + "./packages/melonjs/skills/melonjs-3d-assets", + "./packages/melonjs/skills/melonjs-physics", + "./packages/melonjs/skills/melonjs-sprites-and-animation", + "./packages/melonjs/skills/melonjs-input", + "./packages/melonjs/skills/melonjs-effects-and-shaders", + "./packages/melonjs/skills/melonjs-tilemaps", + "./packages/melonjs/skills/melonjs-audio", + "./packages/melonjs/skills/melonjs-scenes-and-state", + "./packages/melonjs/skills/melonjs-ui-and-text", + "./packages/melonjs/skills/melonjs-particles-and-trails", + "./packages/melonjs/skills/melonjs-camera-and-drawing", + "./packages/melonjs/skills/melonjs-lighting", + "./packages/melonjs/skills/melonjs-deployment", + "./packages/melonjs/skills/melonjs-loading-assets", + "./packages/melonjs/skills/melonjs-renderer-backends", + "./packages/melonjs/skills/melonjs-performance", + "./packages/melonjs/skills/melonjs-events", + "./packages/melonjs/skills/melonjs-plugins" + ] +} diff --git a/README.md b/README.md index 79f9d67c63..6f502464c4 100644 --- a/README.md +++ b/README.md @@ -324,6 +324,46 @@ Transpiling costs some size and speed: private class members become `WeakMap` lookups, which are on hot paths in the renderer. Only reach for it if you have users on browsers that need it. +Building with AI agents +------------------------------------------------------------------------------- + +melonJS ships **skills** — guidance files that teach AI coding assistants the +engine's conventions and, more usefully, the mistakes that fail silently rather +than raising an error. + +They are installed with the package, at `node_modules/melonjs/skills/`, and are +versioned with the engine — so the guidance always matches the release you have. + +**Claude Code** — install as a plugin : + +``` +/plugin marketplace add melonjs/melonJS +``` + +or copy the skills into a project : + +```bash +mkdir -p .claude/skills && cp -r node_modules/melonjs/skills/melonjs* .claude/skills/ +``` + +**Other agents** (Codex, Cursor, Gemini CLI, …) read an `AGENTS.md` from your own +project root. One ships ready to use — copy it across : + +```bash +cp node_modules/melonjs/skills/AGENTS.md ./AGENTS.md +``` + +It points at the shipped skills, names the three rules that produce code which +runs and is wrong, and links the API index below. If you already have an +`AGENTS.md`, paste its sections into yours. + +The skills are plain markdown and can be read by any agent, or by a human. + +For anything the skills do not cover, the complete API is indexed for agents at +[llms.txt](https://melonjs.github.io/melonJS/llms.txt) — every exported class, +function and type with a one-line summary and a link to its reference page, +regenerated on every docs build. + Community ------------------------------------------------------------------------------- Join us and get help or share your projects : diff --git a/packages/melonjs/CHANGELOG.md b/packages/melonjs/CHANGELOG.md index 185ac160b5..597fac3c68 100644 --- a/packages/melonjs/CHANGELOG.md +++ b/packages/melonjs/CHANGELOG.md @@ -1,23 +1,29 @@ # Changelog -## [20.3.0] (melonJS 2) - _unreleased_ +## [20.3.0] (melonJS 2) - _2026-08-31_ ### Added +- Skills for AI coding assistants, shipped in the package at `skills/` and versioned with the engine. Twenty-three topic guides — bootstrap, scenes, renderables, sprites, input, physics, tilemaps, audio, UI and text, shaders, particles, lighting, the 3D tier and its asset formats, loading, backends, events, performance, plugins and the 20.x migration — each ending with a symptom-to-cause table for the failures that are silent rather than fatal: a custom `draw()` that ignores `this.pos`, `isKinematic` blocking pointer events, `.z` set after `addChild`, `removePostEffect()` destroying the effect. Installable in Claude Code via `/plugin marketplace add melonjs/melonJS`; for agents following the `AGENTS.md` convention (Codex, Cursor, Gemini CLI) a ready-made `skills/AGENTS.md` ships alongside them, to copy into a game's project root +- Documentation: an `llms.txt` index is now published with the API reference at , listing every exported class, function, interface, type and namespace with a one-line summary, a link to its page and a marker on the deprecated ones. Regenerated from the TSDoc comments on every docs build, so an agent that cannot find something in a skill has one URL to fetch rather than an API name to guess - Audio: sprite support — a clip can declare named regions of one file (`sprite: { jump: [0, 450] }`) and `audio.play("sfx", { sprite: "jump" })` plays one, so a single download can carry many effects. `play()`'s second argument now takes either the original `loop` boolean or an options object (`sprite`, `loop`, `onend`, `volume`); every existing call form is unchanged - Audio: `duration()`, `playing()` and `state()` report a clip's length, whether it is playing, and its load state - Audio: `pool`, `rate`, `mute`, `preload` and `format` can be set when loading a clip, along with an `on` map of lifecycle callbacks (`play`, `pause`, `stop`, `end`, `fade`, `seek`, `rate`, `volume`, `mute`, `unlock`). Each is forwarded only when set, so existing declarations are unaffected - Device: `platform.appleVendor` reports whether the browser is Apple's WebKit, which `platform.iOS` does not answer — a Mac running Safari is one but not the other +- Input: added `input.setPreventDefault(value)`. `input.preventDefault` is documented as a global option but is a read-only module binding, so assigning to it silently had no effect; the setter is the working form, and `bindKey`'s fourth argument still overrides it per binding ### Changed -- Engine: melonJS has no runtime dependencies. `core-js` provided three language polyfills — `globalThis`, `String.trimStart` and `String.trimEnd` — all of which are ES2019/ES2020 features that predate the ES2022 bundle target by years, so they were no-ops in every environment the engine supports. `globalThis` in particular was added for Node ([#1092](https://github.com/melonjs/melonJS/issues/1092)), which has had it natively since Node 12. Removing it drops 15.6 kB from the bundle and 1.3 MB from every install. Games needing older browsers must transpile *and* polyfill, which is the consumer's build to configure — a library patching `String.prototype` for the whole application was never the right call -- Loader: the shared fetch helper moved from `loader/parsers/` to `utils/`. It is engine transport rather than loader policy, and every asset type — including audio, which needs it for the `file:` fallback — is a peer consumer of it. Internal module, no public API change -- Loader: audio is registered like every other asset type, through a parser in `loader/parsers/`. The loader previously imported the whole audio module to register its load function directly, so it was the one type whose parser was not a parser. It now has no direct dependency on the audio module, which keeps a future backend change from rippling into the loader -- Audio: the engine no longer depends on a third-party audio library. The backend is now maintained in-tree, which removes melonJS's last runtime dependency besides core-js and fixes a long-standing packaging wart: the emitted type declarations referenced the library, so consumers type-checking with `skipLibCheck: false` had to install it themselves +- Audio: the engine no longer depends on Howler, the third-party audio library — the backend is now maintained in-tree. This also fixes a long-standing packaging wart: the emitted type declarations referenced the library, so consumers type-checking with `skipLibCheck: false` had to install it themselves +- Loader: the `onload`, `onProgress` and `onError` properties are removed. Deprecated since 18.2.0, they were `let` bindings on an ES module namespace — `loader.onProgress = fn` threw a `TypeError`, so the documented callback API could never be used by anyone. Use the `LOADER_COMPLETE` / `LOADER_PROGRESS` / `LOADER_ERROR` events, or `preload(assets, onloadcb)` ### Fixed +- Audio: sound files failed to load when the game is served from `file://` — a Cordova or Capacitor APK on Android, most commonly. `fetch()` cannot read that scheme in those WebViews, and audio was the only asset type still calling it directly instead of going through the engine's loader, which falls back to XHR +- Tiled: an object with no explicit geometry got a **triangular** collision shape. The default shape was built from three vertices — `(0,0)`, `(w,0)`, `(w,h)` — so the lower-left half of every plain rectangle in a map was not solid +- Timer: `setInterval`'s `pauseable` argument was ignored — the expression storing it was unconditionally true, so a repeating timer could never be made to keep running while the engine is paused. `setTimeout` was unaffected. A pause-menu animation or a countdown that should not freeze now works as documented ([#1619](https://github.com/melonjs/melonJS/issues/1619)) +- Loader: a failed asset reported `Cannot read properties of undefined (reading 'onError')` instead of `Failed loading resource ` when `preload` was called detached from the `loader` namespace, masking the real failure — the error path read `this.onError` from a module function, which has no `this` +- Lighting: the one-time console warning for normal-map lighting named the WebGL renderer and told you to switch to `video.WEBGL`, which sent WebGPU users chasing a fallback they were not on — WebGPU implements the lit pipeline too. It now names a GPU backend, and says that `Light2d` glow and stage lighting still draw on Canvas +- Docs: a sweep of the API reference against the source it documents. `Renderable#draw` told you to draw at `(0, 0)` when `preDraw` never translates to `this.pos`; the `body` example called `viewport.follow(this.pos, …)`, which throws; `Camera3d` documented a `roll` axis that does not exist; `Mesh` omitted eight constructor settings including `lit`, the switch for the whole lit-mesh path; the `Asset` typedef advertised `"tmj"` / `"tsj"` asset types that throw (those are file extensions under `"tmx"` / `"tsx"`); `Body#addShape`'s documentation had drifted onto a private helper, leaving the public method undocumented; and 18 effect classes carried an unresolvable `@param` import path plus examples teaching `renderable.shader =`, deprecated since 19.2.0. `setBlendMode` also claimed on all three backends that "every renderer supports the full set", when `"none"` needs a GPU backend — on Canvas it resolves to `"normal"`, which `setBlendMode` reports back - Docs: the README gained a short guide on transpiling for older browsers, including the trap that catches people — build setups skip `node_modules` by default, so melonJS is left untouched however low the target is set - Docs: the README described the engine as ES6 and gave no target for the published bundle, which reads as a compatibility claim it does not meet — the bundle uses private class members and ~700 ES2020 constructs, so an ES6-era browser cannot parse it at all. It now states the ES2022 target once, where someone deciding whether to adopt will look. The CDN example also still referenced v19 -- Audio: sound files failed to load when the game is served from `file://` — a Cordova or Capacitor APK on Android, most commonly. `fetch()` cannot read that scheme in those WebViews, and audio was the only asset type still calling it directly instead of going through the engine's loader, which falls back to XHR - Device: `platform.Kindle` missed every current Fire tablet. It required `Silk` followed by `Mobile Safari`, but Chromium-based Silk ends its user agent with `Safari/537.36`; it now matches the `Silk/` token directly ## [20.2.0] (melonJS 2) - _2026-08-29_ diff --git a/packages/melonjs/package.json b/packages/melonjs/package.json index aade6dd5fe..6401415792 100644 --- a/packages/melonjs/package.json +++ b/packages/melonjs/package.json @@ -1,6 +1,6 @@ { "name": "melonjs", - "version": "20.2.0", + "version": "20.3.0", "description": "melonJS Game Engine", "homepage": "http://www.melonjs.org/", "type": "module", @@ -50,9 +50,10 @@ }, "files": [ "build", - "package.json", + "skills", "README.md", - "CHANGELOG" + "CHANGELOG", + "package.json" ], "devDependencies": { "@types/node": "^25.6.0", @@ -76,7 +77,7 @@ "build": "pnpm lint && tsx scripts/build.js && pnpm types", "dist": "pnpm clean && pnpm lint && pnpm vitest run && pnpm build && pnpm doc && cp ../../README.md .", "dist:publish": "pnpm clean && pnpm lint && pnpm build && pnpm doc && cp ../../README.md .", - "doc": "tsx scripts/check-doc-readme.ts && typedoc src/index.ts --tsconfig tsconfig.build.json --readme ../../DOC_README.md --hideGenerator --name 'melonJS' --navigation.includeCategories true --categorizeByGroup false", + "doc": "tsx scripts/check-doc-readme.ts && typedoc src/index.ts --tsconfig tsconfig.build.json --readme ../../DOC_README.md --hideGenerator --name 'melonJS' --navigation.includeCategories true --categorizeByGroup false && tsx scripts/generate-llms-txt.ts", "doc:watch": "typedoc src/index.ts --tsconfig tsconfig.build.json --readme ../../DOC_README.md --hideGenerator --name 'melonJS' --navigation.includeCategories true --categorizeByGroup false --watch --skipErrorChecking --preserveWatchOutput --logLevel Error", "serve": "serve docs", "prepublishOnly": "pnpm dist:publish", diff --git a/packages/melonjs/scripts/generate-llms-txt.ts b/packages/melonjs/scripts/generate-llms-txt.ts new file mode 100644 index 0000000000..d8153427f9 --- /dev/null +++ b/packages/melonjs/scripts/generate-llms-txt.ts @@ -0,0 +1,299 @@ +/** + * Generate `docs/llms.txt` from the TypeDoc output. + * + * The agent skills under `skills/` cover the engine by topic, but they are + * hand-written and deliberately partial. `llms.txt` is the escape hatch: a + * machine-readable index of the *whole* generated API reference, regenerated on + * every docs build, so an agent that cannot find something in a skill has one + * URL to fetch instead of guessing at class names. + * + * Format follows llmstxt.org: an H1, a blockquote summary, then sections of + * `- [name](url): summary` links. Summaries come from the same TSDoc comments + * the HTML pages render, so this cannot drift from the docs it indexes. + * + * Runs after `typedoc` in the `doc` script; `docs/` is what the Pages workflow + * publishes, so the file lands at . + */ +import { readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const docsDir = resolve(here, "../docs"); +const baseUrl = "https://melonjs.github.io/melonJS/"; + +/** TypeDoc's output directories, in the order they are listed in the index */ +const sections = [ + { dir: "classes", title: "Classes" }, + { dir: "functions", title: "Functions" }, + { dir: "interfaces", title: "Interfaces" }, + { dir: "enums", title: "Enumerations" }, + { dir: "types", title: "Type aliases" }, + { dir: "variables", title: "Variables and namespaces" }, + { dir: "modules", title: "Modules" }, +]; + +const entities: Record = { + "&": "&", + "<": "<", + ">": ">", + """: '"', + "'": "'", + " ": " ", +}; + +/** + * Drop every tag from a fragment of HTML. + * + * Deliberately not `replace(/<[^>]+>/g, "")`: a single regex pass over a tag + * shape is an incomplete sanitizer — an unterminated `` cannot leave a + * tag behind, and an unterminated one takes the rest of the fragment with it. + * + * A bare `<` in rendered HTML is always a tag start; typedoc encodes literal + * ones as `<`, which is still encoded at this point since entities are + * decoded afterwards. + * @param html - a fragment of TypeDoc HTML + * @returns the fragment with all markup removed + */ +const stripTags = (html: string): string => { + let text = ""; + let at = 0; + while (at < html.length) { + const open = html.indexOf("<", at); + if (open === -1) { + text += html.slice(at); + break; + } + text += html.slice(at, open); + const close = html.indexOf(">", open); + if (close === -1) { + break; + } + at = close + 1; + } + return text; +}; + +const toText = (html: string): string => { + return stripTags(html) + .replace(/&[a-z#0-9]+;/g, (match) => { + return entities[match] ?? " "; + }) + .replace(/\s+/g, " ") + .trim(); +}; + +/** + * The substring of `html` covered by the div opening at `from`, closing tag + * included. Regex cannot match nested `
`s, and TypeDoc nests them. + * @param html - the page source + * @param from - index of the opening ` { + const tag = /<\/?div\b/g; + tag.lastIndex = from; + let depth = 0; + let match = tag.exec(html); + while (match !== null) { + depth += match[0] === "".length); + } + match = tag.exec(html); + } + return html.slice(from); +}; + +/** + * The first documented paragraph inside a block of TypeDoc HTML. + * + * `@deprecated` / `@see` render as `tsd-tag-*` blocks *inside* the comment, so + * they are lifted out first — otherwise a symbol whose only comment is + * `@deprecated since 17.1.0` gets indexed with "since 17.1.0" as its summary. + * Parameter and return blocks are cut away for the same reason: they carry + * comments of their own, and one of those is not a summary of the symbol. + * @param block - a fragment of TypeDoc HTML + * @returns the paragraph as plain text, or an empty string + */ +const firstParagraph = (block: string): string => { + const body = block + .split('
'); + while (at !== -1) { + let comment = balancedDiv(body, at); + for (;;) { + const tagAt = comment.indexOf('
([\s\S]*?)<\/p>/); + if (paragraph !== null) { + const text = toText(paragraph[1]); + if (text !== "") { + return text; + } + } + at = body.indexOf('
', at + 1); + } + return ""; +}; + +/** + * Whether the page documents something this repository actually defines. + * + * TypeDoc walks into the type of an exported value, so a `string` constant like + * `loader.nocache` contributes a page for every `String.prototype` method — + * 49 of them, all pointing at `lib.es5.d.ts`. Indexing those would tell an + * agent that `loader.nocache.trimEnd` is part of the melonJS API. + * @param html - the page source + * @returns true when at least one source link points into the repository + */ +const isOwnSymbol = (html: string): boolean => { + const sources = [ + ...html.matchAll(/