# Native modding The modding book lives on the [project wiki](https://github.com/bryanthaboi/pokemon-gen1-recomp-project/wiki). - [Getting started](https://github.com/bryanthaboi/pokemon-gen1-recomp-project/wiki/Getting-Started) — install a mod, write a first one, enable and disable it. - [Tutorials](https://github.com/bryanthaboi/pokemon-gen1-recomp-project/wiki/Tutorials) — twelve dependency-ordered rungs, each a runnable mod. - [Cookbook](https://github.com/bryanthaboi/pokemon-gen1-recomp-project/wiki/Cookbook) — task-sized recipes. - [Registry reference](https://github.com/bryanthaboi/pokemon-gen1-recomp-project/wiki/Reference-Registries) — every registry, generated from `src/mods/Schemas.lua`. Regenerate the reference straight into a wiki checkout: ```sh luajit tools/gen_registry_docs.lua ../pokemon-gen1-recomp-project.wiki ``` ## Rendering pipelines Most registries hand the engine *content*. `render_pipelines` hands it *drawing*: a pipeline is a display mode a mod owns, which may replace the overworld's world pass with geometry of its own and/or post-process the finished image. `mods/voxel_world` is the worked example — a 3D diorama overworld plus a tilt-shift miniature pass, in about 120 lines of glue over its renderer. A record declares what the mode *is*; the engine (`src/render/Pipelines.lua`) supplies everything about *being a display mode*: the OFF/1/2/3 ladder, an options row next to TILT, a hotkey, persistence in `save.options.pipelines`, and the rule that a world pipeline and the engine's own TILT are mutually exclusive. ```lua mod.content.render_pipelines:register("diorama", { label = "DIORAMA", -- options row label levels = { "OFF", "15", "35", "50" }, -- ladder; defaults to OFF/ON hotkey = "6", -- checked after the engine's keys priority = 20, -- highest eligible wins the world available = function() return Renderer3D.ok() end, update = function(dt, level) Camera.ease(dt, level) end, drawWorld = function(ctx) return renderScene(ctx) end, }) ``` Three draw stages, each optional; a record needs at least one: | stage | signature | runs | | --- | --- | --- | | `drawWorld` | `(ctx) -> canvas \| nil` | instead of the flat/tilt world pass | | `worldPresent` | `(canvas, ctx) -> canvas` | over the world, **before** the UI composites | | `present` | `(canvas, ctx) -> canvas` | over the whole frame, world and UI alike | `worldPresent` is the one to reach for when an effect must leave dialog boxes and menus crisp — a depth-of-field or colour grade on the world only. `present` is for effects that genuinely own the screen, like a CRT curve. `ctx` carries the frame: `state`, `cam`, `vw`/`vh` (world-pixel view), `width`/`height` (window pixels), `scale`, `level`, `paletteFor(map)` and `spriteColors(map)`. It also carries `ctx.drawFx(project, scale)` — call it with your own projection and the engine draws every active field effect (the "!" bubble, the Poké Center heal machine, the Fly bird, the fishing rod, Rock Tunnel darkness) at its correct anchor under your camera. There is exactly one copy of each effect, so a new engine effect works in your pipeline without you touching anything. Three rules worth knowing: - **`gate` governs input, never the draw.** It decides whether the player may *change* the mode (default: free-roam overworld only). A mode that stopped rendering during a warp would flash the flat 2D world every time the player walked through a door. - **`available` is re-read every frame** and is the only thing that decides whether the mode can render at all. Answer `false` on a headless run or a driver with no depth canvas and the engine silently keeps the vanilla 2D path — which is why shipping a pipeline enabled is safe. - **A callback that throws retires its pipeline**, attributed to your mod in the manager's error feed, and the frame falls back to 2D. A broken renderer costs the player a display mode, never the game. Returning `nil` from `drawWorld` is a normal answer meaning "not this frame"; the engine draws the vanilla world instead. ## Battle sprite scaling The enemy's front pic draws at 1x and the player's back pic at 2x, the way the Game Boy did. A mod can override either, per species or per image. Per species, on the `pokemon` record: ```lua -- MEW's back pic renders 1.5x; its front pic is untouched mod.content.pokemon:patch("MEW", { battleScaleBack = 1.5 }) ``` `battleScaleFront` scales the enemy pic, `battleScaleBack` the player pic; both take a number in `0.25 .. 4.0`. Per image, on the `battle_sprite_scales` registry, keyed by the asset path exactly as the data references it: ```lua mod.content.battle_sprite_scales:register("abra_back", { path = "assets/generated/battle/back/abrab.png", scale = 1.5, }) ``` An image-level entry beats the species scale for that one pic, and it is the only way to scale a pic that is not species-keyed — the player's trainer back sprite, held on screen until "Go!", is a bare image path. The resolution order at draw time is **image-level → species-level → default** (1x front, 2x back). - **The pic stays grounded at every scale.** The player pic keeps its feet flush on the text-box top (`y = 96`); the enemy pic keeps its bottom edge and horizontal centre pinned in its 7×7 slot. A larger pic grows upward and outward from that anchor, never off the shelf. - **Scaling composes with the send-out grow.** The `AnimateSendingOutMon` ball-to-pic grow multiplies your scale through each stage, so a rescaled mon still grows into place from the ball, grounded the whole way. ## Developer console Boot with developer mode on to unlock the in-game console and hot-reload hotkeys. Either set `POKEPORT_DEV=1` in the environment or pass `--developer` on the command line: ```sh love . --developer ``` While developer mode is active: - `` ` `` (backtick) opens the console overlay — a Lua REPL with `game`, `data` and `mods` in scope. Press `` ` `` again to close it. - `F5` hot-reloads mods and asset caches without restarting. The console understands these verbs (anything else is evaluated as Lua): - `warp MAP [x y]` — teleport to a map (default cell 5,5). - `give ID [n|level]` — add an item (count) or a Pokémon (level). - `flag NAME [on|off]` — read or set an event flag. - `party` — dump the current party. - `mods` — list loaded mods and their state. - `reload` — hot-reload mods (same as `F5`). - `trace PAT | trace off` — trace events/hooks matching a glob pattern. - `help` — list the verbs. Developer mode also arms the mod loader's dev tripwire, which flags mods that reach outside their permission set.