src/content.js is the main modding file. The engine in src/game.js reads exported data from it, so most visual and gameplay content changes can happen there without touching engine code.
If you do want to read the runtime side, it is now split by responsibility:
src/state.js: run setup, map randomization, save data, and monster creationsrc/drills.js: lesson drill content and setupsrc/drill-runtime.js: how drill cursor movement, prompts, and insert-mode fixes worksrc/battle.js: catch flow, enemy turns, party switching, and battle outcomessrc/battle-challenges.js: authored battle mini-drill templatessrc/input.js: command mode, rename mode, VimTree controls, and key normalizationsrc/overworld.js: overworld movement, gate transitions, and NPC/sign interactionssrc/progression.js: lesson completion checks, objective text, and gate textsrc/render.js: canvas drawing primitives and text wrappingsrc/scenes.js: scene composition and shared HUD layoutsrc/scene-tree.js: VimTree overlay renderingsrc/scene-drill.js: lesson deck and drill overlay renderingsrc/scene-battle.js: battle scene, command window, and battle mini-drill overlay renderingsrc/game.js: how those pieces work together during play
- Change
PLAYER_STYLEto recolor the hero. - Change
NPC_STYLESto recolor mentors. - Add a new
createCreature(...)entry toCREATURES. - Edit
LESSONS,MAPS, andWORLD_COLORSto change what the player learns and how the world looks. - Use CONTENT_GUIDE.md when you want the shortest route for adding content without reading every runtime file.
Use createCharacterStyle(...) to override only the colors you care about.
export const PLAYER_STYLE = createCharacterStyle({
clothes: "#3666d6",
hair: "#ff7b72",
accent: "#8bc5ff",
});The available character color slots are:
outlineclotheshairaccentskinboots
NPCs use the same shape and the same color slots:
export const NPC_STYLES = {
mentor: createCharacterStyle({
clothes: "#556f96",
hair: "#ffe07a",
}),
coach: createCharacterStyle({
clothes: "#705e8a",
hair: "#ff9ac0",
}),
sage: createCharacterStyle({
clothes: "#3d735c",
hair: "#f6f2cf",
}),
};Add a new object to CREATURES with createCreature(...).
createCreature({
id: "sparkit",
name: "Sparkit",
baseHp: 18,
baseAttack: 6,
sprite: [
"..33..",
".3443.",
"344443",
".2332.",
"..22..",
],
palette: {
"1": "#182033",
"2": "#ffd166",
"3": "#fff4d1",
"4": "#ff8d58",
},
frames: [
[
"..33..",
".3443.",
"344443",
".2332.",
"..22..",
],
[
"..33..",
".3443.",
"344443",
"..332.",
"..22..",
],
],
}),Notes:
spriteis the default frame.framesis optional. If you omit it, the engine usesspriteas a single-frame creature.- Each row is a string. Each character maps to a color in
palette. .means transparent.
Use createLesson(...) inside LESSONS:
createLesson({
id: "grove",
title: "Lesson 4: Counts",
body: "Meet Sage Count, use counted motions like 3w and 2j, then climb onward.",
}),Maps are defined with createMap(...).
export const MAPS = {
meadow: createMap({
name: "Word Meadow",
theme: "meadow",
rows: [
"####################",
"#D==S======,,,,....#",
"#....==.....,,,,...#",
"#...............==R#",
"####################",
],
}),
};Tile rows are plain strings, so the fastest way to teach level design is to sketch the layout directly in code.
The lesson editor content lives in src/drills.js.
- use
type: "motion"for cursor movement checks likew,e,2j, orgg - use
type: "insert"fori ... Escrepairs - use
type: "edit"for edits likex,dd, orcw - use
type: "search"andtype: "replace"for/and:sdrills
That split keeps lesson content readable without mixing it into src/game.js.
The shared UI and environment palette lives in WORLD_COLORS.
export const WORLD_COLORS = {
skyA: "#7ec8ff",
skyB: "#d6f3ff",
grassA: "#5abf5a",
grassB: "#8fe36d",
panelAccent: "#ffd166",
// ...
};That is the easiest place to theme the game without touching render logic in src/game.js.
RANDOMIZATION_RULES controls which maps reroll obstacles between runs and which marker tiles must remain reachable.
export const RANDOMIZATION_RULES = {
meadow: {
start: { x: 1, y: 1 },
markers: ["M", "R"],
maxBlocks: 9,
},
};- Make content edits in
src/content.js. - Only open
src/game.jsif you need top-level orchestration or system wiring. - Use
src/overworld.jsfor map flow and NPC interaction changes. - Use
src/scene-battle.js,src/scene-tree.js, orsrc/scene-drill.jsfor specific UI/overlay work. - Run
node --check src/game.jsandnode --check src/content.js. - Run
npm run smoketo catch runtime wiring mistakes. - Start the local server with
node server.js.