These instructions are for making changes in the LittleJS repo safely. Optimize for small diffs, clarity, and ease of use.
- Prefer minimal, local changes. Do not refactor for style unless asked.
- No new runtime dependencies. Keep LittleJS dependency-free at runtime.
- Do not hand-edit generated build artifacts.
- Treat
dist/as generated output. - Make changes in
src/(andplugins/when appropriate), then run the build.
- Treat
- Match surrounding style. Follow the conventions in the files you touch.
- Avoid breaking public APIs. If a change could break users, call it out clearly and offer a compatible alternative.
- Keep agent-generated working files under
.claude/.docs/is the published JSDoc API site — do not write into it. Superpowers plans go in.claude/superpowers/plans/and specs in.claude/superpowers/specs/(overrides the skill defaults). The.claude/folder is gitignored.
If anything in this doc conflicts with the actual repo behavior, follow the repo behavior and update this doc.
README.md- Overview and getting startedREFERENCE.md- API quick referenceexamples/- Working examples demonstrating engine features
LittleJS is a modular HTML5 game engine with:
- Core engine:
src/engine*.js(main loop, objects, rendering, physics, input, etc.) - Plugins:
plugins/*.js(optional features like Box2D, post-processing, UI, audio helpers, etc.) - Build system:
src/engineBuild.mjs(concatenates modules into distributable bundles)
- Modular architecture (one subsystem per file)
- Concatenated at build time (internal source does not use ES modules)
- Code is vanilla JavaScript with type info expressed via JSDoc comments
Common outputs include:
littlejs.js- Full bundle (debug features included)littlejs.release.js- Production bundle (debug stripped)littlejs.esm.js- ES module build (import/export)littlejs.esm.min.js- Minified ES modulelittlejs.d.ts- TypeScript definitions
Use via script tag or ES module import:
<script src="dist/littlejs.js"></script>import * as LJS from './dist/littlejs.esm.js'
Prefer adding new optional features as plugins when it keeps the core simpler.
examples/starter/- Plain JavaScript global usage via<script>(recommended starting point)examples/module/- ES module import patternexamples/typescript/- TypeScript example usageexamples/shorts/*.js- Single-file demos loaded by the shorts harness
Short examples are special:
- Pure JS code file, no HTML
- No imports, do not use LJS namespace - engine APIs are available globally
- Override hooks:
gameInit(),gameUpdate(),gameUpdatePost(),gameRender(),gameRenderPost()
Prefer factory functions for core types:
vec2(x, y)notnew Vector2(x, y)rgb(r, g, b, a)orhsl(h, s, l, a)notnew Color(...)tile(index, size)for tile info
Use constructors for game objects and complex types:
new EngineObject(pos, size)new ParticleEmitter(...)new Sound(zzfxParams)new Timer(duration)
camelCasefor variables and functionsPascalCasefor classesUPPER_CASEfor constants that are truly constant (likePI)
- Use JSDoc with
@memberofgrouping (namespaces: Engine, Math, Draw, Input, Audio, Debug, Settings, etc.) - Prefer single-line comments:
// comment - Use
ASSERT(condition, 'error message')for validation (stripped in release) - Use
LOG(...)for debug output (stripped in release)
Use built-in type helpers for validation:
isNumber(n) // true if number and not NaN
isStringLike(s) // true if stringifiable (has toString returning a string)
isArray(a) // true if array
isVector2(v) // true if valid Vector2
isColor(c) // true if valid ColorEngine source exposes short aliases for common Math.* calls — prefer them
over Math.X in engine and plugin code:
abs, floor, ceil, round, min, max, sign, hypot, log2, sin, cos, tan, atan2, PIFor things without an alias (e.g. Math.trunc, Math.SQRT2), use Math.* as normal.
- Engine time:
time,timeReal,frame,timeDelta - Camera:
cameraPos,cameraScale,cameraAngle - Input:
mousePos,mousePosScreen,mouseWheel - State:
paused,debug,debugOverlay - Settings are in
engineSettings.jswith corresponding setter functions
function gameInit() { } // Called once after engine starts
function gameUpdate() { } // Called every frame for game logic
function gameUpdatePost() { } // Called after physics, even when paused
function gameRender() { } // Called before objects render
function gameRenderPost() { } // Called after objects render
engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRenderPost, ['tiles.png']);class Player extends EngineObject {
constructor(pos) {
super(pos, vec2(1), tile(0, 16));
this.setCollision();
}
update() {
super.update();
// custom logic
}
}drawRect(pos, size, color) // solid rectangle
drawTile(pos, size, tileInfo, color) // sprite from tile sheet
drawText(text, pos, size, color) // text rendering
drawLine(posA, posB, thickness, color) // line between points
drawEllipse(pos, size, color) // filled ellipse- New public APIs must be added to
src/engineExport.js- Variables and functions added to engine source files are accessible in script-tag builds automatically, but the ESM build (littlejs.esm.js) and TypeScript definitions (littlejs.d.ts) only include what's listed inengineExport.js. Plugin exports go inplugins/pluginExport.js. Forgetting this means ESM/TS users can't access the new API. - ASSERT and LOG are stripped in release builds - Don't rely on side effects
- Don't modify constant colors -
WHITE,BLACK,RED, etc. are frozen; use.copy()first - Time variables are global -
time,frameupdate automatically each frame - Fixed 60 FPS timestep - Physics runs at 60 FPS regardless of display refresh rate
- WebGL is enabled by default - Set
glEnable = falsebeforeengineInit()for Canvas2D only - Tile coordinates are bottom-left origin - Y increases upward in world space
npm run buildnpm test- Tests target
dist/littlejs.esm.js— rebuild withnpm run buildafter changing source. - test/setup.mjs stubs minimal DOM and enables headless mode. Tests shouldn't call
engineInit,render(), or assumetimeadvances. - Zero test dependencies — uses Node's built-in
node --test. Match the style in test/ when adding new ones. - CI runs build + test on every push/PR (.github/workflows/test.yml).
- Press
Escto toggle debug overlay - Number keys toggle visualizations
+/-keys control time scale- Debug functions:
debugRect(),debugCircle(),debugLine(),debugText()