Camera: mount the render-kit host with inset:0 (fill #game-display) instead of
min(100%,78vh)+translate(-50%) centering. #game-display is grid-bounded (container is
100vh/overflow-hidden), so filling it gives the camera reliable clientWidth/Height every
frame — fixes the intermittent "player stranded in a top band, black below" off-centering.
Torches (in-game): drop the pack torch SPRITE composite — several sheets place it off-center/
rotated, reading as misaligned. Keep wall base + procedural RK.fx flame: a clean wall-mounted
torch that looks right in every pack.
Wait animations: shrink the juice bursts (18-24 -> 6-7 particles, spread 1.8-2.4 -> 0.6-0.7)
and remove the red death/caught bursts, so a grab/escape reads as a tight glint on the hero
instead of an explosion spraying onto the wall torches.
Torches replace WALL cells adjacent to floor (dungeon.js), but the Tiled renderer based the
torch on a floor tile and drew only a procedural flame — so they read as flames floating on
the floor. Base the dungeon torch on `wall` and draw the pack torch sprite over it, then RK.fx
animates the flame/glow on top: a proper wall-mounted sconce. Fire fixtures now composite
base + sprite + flame (hazards keep base + pulse, no sprite).
The render-kit "waiting room" replaced the beloved scripted drama (player races for a
pulsating treasure, grabs it, escapes or is devoured by a monster pack, death shockwave +
ESCAPED! celebration, randomized variants) with a static lit room — a big downgrade. Restore
BOTH ROT waiting animations (block-wait AND awaiting-payment, selected via Game._awaitingPayment)
by hiding the render-kit host and letting the ROT display own the waiting screen. Remove the
now-unused SP.showWaiting/hideWaiting/waitingScene bridge code. The pack/mode still applies in
actual gameplay, which is what matters.
The next-block wait now draws a small lit "mining room" Scene through the ACTIVE renderer
(RK.SPGame.showWaiting) — floor/walls/torches in your chosen pack (Tiled/Iso/3D), camera
centered, torches flickering via the FX loop — with the "Mining the next block" text as an
overlay. So the wait inherits your tiles/mode instead of being the plain ROT animation.
Falls back to the ROT WaitingScreenAnimator if the kit is unavailable. SP.render clears the
overlay when the real game starts.
- Modal: "Safe — full block window" overstated it (blocks arrive randomly, Poisson). Now
"Start fresh · blocks land at random" / "Jump in · race the time left".
- Camera: was snapping to the player every frame (jerky on canvas resize / level teleport).
Now glides (lerp 0.3) and only snaps on a big jump; snap-centers on new-game start.
The player-centered camera snapped the dungeon to the top-left corner after a while: the
renderer nulled focusPoint on a transient player-less frame, so the transform lost its
translate. Fix: focusPoint is now sticky (keep the last if the player is momentarily
absent), and the SP bridge re-applies the camera every frame via its own RAF loop, so a
canvas resize or a stale frame can no longer break centering.
- FOG: sceneFromGameState only emits exit/entrance/treasure/items once their cell is EXPLORED,
and the monster only while IN VIEW — no more exit stairs leaking through the fog.
- DESCEND FEEDBACK: reaching a non-final exit descends (multi-level) and used to silently dump the
player at a new entrance (read as a bug). Server now sends depth/maxDepth on game_event; the
client shows "Stairs down! Level N of M — keep going". Also lowered level counts (WOW 4->2,
BTC 8->3, XMR 2->1) — clearing 4-8 levels in one block was unwinnable; kept modest until proper
multi-level calibration + UX.
- CAMERA: the host filled the very-tall #game-display so centering put the player low; it's now a
bounded viewport (min(100%,78vh)) centered in #game-display, so the player stays on-screen-centered.
Suite 62/435 green.
The `original` pack was built on the Kenney sheet, so it didn't match the classic look the
ROT instructions + waiting screens use. Point it at html/tiles.png (32px, the legacy
tileMap sheet) with grid coords = legacy pixel coords / 32: floor [0,0], wall [2,1], door
[1,0], torch [32,1], treasure [30,0], plus the classic @ hero [2,0] and ~ goblin [31,1].
The tiled renderer now prefers a pack's player/monster TILE over the character sprite in the
DUNGEON (tavern keeps sprites), so the Original pack is fully classic. Suite 62/435 green.
The render-kit dungeon host was a flex item in a centered flex container (.rotdis) with
sibling canvases, so it got shrunk + shoved into a corner; and .rotdis canvas has
max-width:100% !important which clamped the camera canvas. Fix: host is now
position:absolute inset:0 (fills #game-display, which is position:relative), and the
canvas overrides max-width via setProperty(..., important). Dungeon now renders centered
on the player in the game area.
Commit to the render kit as THE single-player renderer (legacy = hard fallback only). Fixes
the two things that broke it:
- CAMERA: each renderer now exposes focusPoint (the player's pixel pos); the SP bridge mounts
a self-sizing 16:10 viewport host and CSS-transforms the canvas to centre the player at a
readable zoom (wheel to zoom). Renderer-agnostic (tiled/iso/3d). No more whole-level-in-a-corner.
- MONSTER: the tiled renderer drew a hardcoded red dot; now it draws the Goblin character sprite
(char-goblin, the same path the tavern draws characters), red orb only as last-resort fallback.
Re-enabled: _drawGameScreen prefers RK.SPGame; spRenderPicker ENABLED. Suite 62/435 green.
NEXT (still owed): make the splash + both waiting animations render through the kit too, so
they use the chosen pack/mode instead of the ROT text screens.
The default->char-villager coercion (grey-circle guard) applied to legacy-rot too, so an
un-customized single-player showed a villager sprite with no way back to the classic hero.
Exclude legacy-rot from the coercion: default/color appearances now fall through to
RenderEngine's original @ tile fallback (chars/skins still draw their sprites; the tavern
canvas path keeps the villager to avoid grey dots). Updated the splash-legend test to the
new tile-based legend. Suite 62/435 green.
The migration that makes the pack ladder pay off IN the dungeon, not just the tavern.
The SP game now draws via RK (Tiled/ASCII/Iso/3D + unlocked packs) instead of the
legacy ROT display.
- Adapter: sceneFromGameState now consumes the CLIENT state shape (sparse {y:{x:v}}
tile maps, not just server arrays) via maxDims(). Verified + unit-tested.
- Bug fix: RK.sceneFromGameState was never actually exposed (only RK.scene.*), so
matchClient + tavern spectator were silently no-op-ing. Aliased onto RK.
- Bridge (spGameRenderer.js, RK.SPGame): mounts an RK renderer in a dedicated host
inside #game-display, toggles the legacy ROT canvases, zoom-enabled. Game._draw
GameScreen prefers it and FALLS BACK to RenderEngine on any failure, so the live
game can never blank. ROT still draws the splash/win/lose text screens.
- Picker (spRenderPicker.js): homepage mode + pack selector, gated by entitlements
(free = Tiled/Original + ASCII; ladder unlocks Iso/3D/packs), rebuilds on identity.
- index.html loads the render-kit renderers + the bridge/picker.
Bundled with the catalog ladder (migration 028): free Original bare pack, ladder
1/5/10/20/40/50. Deploying makes the free tavern bare (by design) AND the dungeon
render-kit-ed. v1 renders the whole explored level + zoom (player-centered camera is
the next polish). Suite 62/435 green; adapter smoke-verified.
Nails down the premium unlock system per the design pass:
- FREE `original` topdown pack (NEW): the game's bare tiles — floor/wall/door/
torch/features only, no furniture, so the free tavern reads plain (the intended
"reason to unlock"). Registered in assetPacks (RK.THEMES.original) as the free
default topdown pack; plain ASCII stays free (accessibility).
- Loyalty ladder (lifetime credits PURCHASED, cumulative, NOT deducted → buying to
play also unlocks cosmetics): Interior 1, skins 5, iso 10, +tilepack 20, +iso 40,
3D 50. Each row is data (tilepack OR skin) → operator appends more at any rung.
DEFAULT_CATALOG (entitlements.js) + migration 028 + MONETIZATION.md ladder table.
- Tests updated to the new thresholds (catalogEntitlements_ultra, directEntryCredit_ultra).
Note: this reprices roguelike-interior from free → 1cr, so once deployed the free
tavern becomes bare (by design). Deploy is bundled with the single-player render-kit
migration so the free-tier change lands with its payoff. Suite 61/432 green.
- Splash legend (screenManager.js): every row now uses a uniform 2-cell icon slot at
a shared left edge and a single fixed text column, so the four icons + descriptions
line up regardless of glyph width (was: per-row span math + leading-space hacks →
ragged columns, player sprite offset).
- Character creator was already on the homepage but read like an admin control: the
button showed the DEFAULT character label "Operator". Renamed the default to
"Adventurer" (charCustomize.js), switched the icon 🧍→🎭, and gave it a clear
"Create / customize your character" tooltip so it announces itself.
The shared "juice" layer, pure-canvas (CSP-safe, no WebGL/shaders), driven off a
clock so both renderers light a scene the same way:
- html/js/render/fxLayer.js (NEW): RK.fx — layered-sine flame flicker, additive
warm glow, a dancing two-layer canvas flame, and pulsing hazard overlays
(lava/poison/spikes) with a footprint-clip callback so it fits square cells
and iso diamonds alike.
- Tavern now has FIRE: 4 corner braziers + a hearth on the left wall throw real
flickering light. Placed along walls, clear of spawn/aisles/tables.
- Both renderers: torch/hearth/hazard tiles draw only their FLOOR base in the
static pass (no more solid amber block), then RK.fx paints the animated flame
/ hazard pulse in the live loop. Tiled caches emitter positions and draws them
over the vignette so light punches through the dark edges; iso adds them as
depth-sorted fx items and keeps its RAF alive while any fire/hazard is onscreen.
- Dungeon hazard zones wired end-to-end in the scene model (lava L / poison P /
spikes ^ chars -> pulsing tiles) — they render the moment the generator emits
them; no client-authoritative fakes.
- fxLayer.js included in tavern.html + match.html.
Suite 58/419 green; render files node --check clean.
Fixes the "thin disconnected slabs" on the top-left/bottom-right walls and juices
the iso scene:
- Wall facing: iso walls have an orientation. A run along grid-x uses the _S
rotation, a run along grid-y uses the perpendicular _W (I viewed the tiles:
_S faces SW, _W faces SE — the two camera-facing rotations), and true corners
use the corner tile. The renderer picks per-cell from neighbours. Enclosing
walls now read as continuous faces on all four edges. Same logic gives
windows/doors a Y rotation.
- Floor variety: deterministic per-cell hash sprinkles planksBroken into wood
floors and stoneTile/stoneUneven into stone — no more flat identical sea.
- Juice: soft contact shadows ground every prop + character, and a warm radial
vignette lights the room instead of it looking pasted onto black.
- Tavern map: barrels/crates line the side walls (clear of windows/spawn, via
the wall-safe put()) so the lobby reads lived-in in every render mode.
Suite 58/419 green.
A — RICH ISO. The old iso-dungeon was a 7-tile sliver of Kenney Isometric
Dungeon; wire the full set so town/dungeon are RICH and interchangeable:
- Real architecture: full stoneWall, stoneWallWindow (actual window),
stoneWallDoorOpen (real door), archway, wood column, stairs.
- Distinct furniture per kind: table -> tableShortChairs (table+chairs),
keg/barrel -> real barrel, crate -> woodenCrate, shelf -> woodenCrates,
chest -> chestClosed (added to the floor+prop compositor). No more
everything-is-a-chest.
Facing (was 90 deg off, twice): verified against live movement the sprites are
angled CLOCKWISE from NE (0=NE,2=SE,4=SW,6=NW), so up->Male_0, right->Male_2,
down->Male_4, left->Male_6. Now the run anim points the way you actually move.
ASCII picker no-op: ASCII draws glyphs and consumes no tile pack, so
projForMode(ascii)=null and the packbar hides for it (it only ever made sense
for Tiled/Iso).
Suite 58/419 green.
Modes are TECHNIQUES; 'Fancy' (tiles+FX) was really a topdown PACK, so:
- Remove 'fancy' from RENDER_MODES (saved 'fancy' falls back to tiles; its dead
Pixi branch is short-circuited by canUseMode). Modes = Tiled/ASCII/Iso/3D.
- Modes now carry a ; a premium mode unlocks once the user has ANY
unlocked pack for that projection (not a single hardcoded pack) — needed now
that iso is multi-pack.
- New 'roguelike-dungeon' topdown pack: same sheet, a DUNGEON palette (stone
floors + barrels/crates vs the wood interior) — a distinct 2D tileset with zero
new assets. Registered + catalog (migration 027, topdown tier 1 / 1 credit).
The tiled renderer's lighting/shadows/vignette are the built-in FX.
- Also: iso facing fix (Male 0=NW..6=NE) reflected in tests.
So the Tiled pack picker now offers Roguelike Interior <-> Dungeon, and Iso offers
Dungeon <-> Medieval — packs as products, proven in the best-looking mode too.
Suite 58/419 green.
Male_0..7 are 45-degree renders (viewed: 0=NW, 2=SW, 4=SE, 6=NE). In this iso
projection grid up->NE, down->SW, left->NW, right->SE, so map facing to the visual
movement direction. Applies to both iso packs (shared characters).
Proves the packs-as-products loop end to end:
- Copied Kenney Isometric Medieval Town wood tiles (floor/floor2/wall/window/
door/banner) into assets/kenney/iso-medieval/.
- Registered 'iso-medieval' as a 2nd iso RENDER pack (its own tile geometry —
Medieval tiles are shorter than the dungeon set; reuses the dungeon characters).
Strong on architecture; no medieval furniture at this scale, so furniture kinds
fall back to floor for now (shines most in the dungeon).
- Catalog product: DEFAULT_CATALOG + migration 026, iso tier 2 / 15 credits.
- PACK PICKER in the tavern: when a projection has >1 unlocked pack, a selector
appears to switch live (setActivePack -> re-render). Uses ?unlock=1 to preview.
So: switch Iso ★, and the pack picker lets you flip iso-dungeon <-> Medieval Town
live. Adding the next pack = assets + registerPack + a catalog row. Suite 58/419 green.
The engine behind 'add more 2D tilesets / iso packs / 3D skins as products'. A
pack is a self-contained render environment for a projection (topdown/iso/3d);
because scenes are kind-based, the SAME pack renders both town and dungeon
(interchangeable), and each maps to a catalog id (monetizable, entitlement-gated).
- packRegistry.js: registerPack / packsForProjection / unlockedPacks (canUsePack)
/ activePackId (saved-if-unlocked else first unlocked) / setActivePack /
activePackAssets. Per-projection active-pack selection, persisted. In-memory
store fallback so it's testable under Node.
- assetPacks: registers the 3 built-ins (roguelike-interior topdown, iso-dungeon,
kenney-3d-characters) + activeIsoAssets()/activeThreeAssets() resolvers; RK.theme()
now defaults to the active topdown pack.
- isoRenderer + avatarVisuals (iso/3d character lookup) read the ACTIVE pack.
- packRegistry loaded before assetPacks in tavern + game.
Backward-compatible: one pack per projection resolves to today's defaults — nothing
changes visually. Adding a pack now = registerPack(...) + assets + a catalog row.
Tests: +5 (register/list, unlock filter, active/saved/fallback, refuse locked).
Suite 58 suites / 419 tests green.
The iso renderer only drew floor+wall (bar/table hardcoded). Generalize the
floor-base + prop compositing to all furniture kinds (bar/table/chair/keg/shelf/
barrel/crate) and map them to the iso-dungeon tiles: stacked barrels = the bar +
kegs, chest = tables/crates/shelves, chair = chairs. Half walls kept (full walls
would occlude the front row). Same coherent iso-dungeon art set. Client-only.
Reusable zoom control (zoomControl.js): RK.attachZoom(host) adds wheel/pinch zoom
to a render host; RK.applyZoom(host) scales whatever canvas is mounted (2D or
WebGL) via CSS transform (pixelated so pixel art stays crisp), re-applied on mode
switch. Trackpad pinch = ctrl+wheel, handled the same way; double-click resets.
Wired into the tavern stage, so it works across Tiled/ASCII/Fancy/Iso/3D. Client-only.
- catSprites: scale the cat to ~2.7x its cell (the sprite is ~40% cat, 60%
transparent padding) so it reads tile-sized instead of tiny.
- tavernMap: remove the centre rug — tile 923 renders green, which looks wrong on
the wood floor. (Re-add with a verified warm rug tile later.)
The black boxes around furniture/windows were transparent object tiles drawn
straight onto the dark canvas. Fix at the pipeline level, not as a tavern one-off:
- tileRenderer: a tile kind whose legend declares `over` (the ground it sits on)
now draws that base tile FIRST, then the object on top — so the object's
transparent areas show the floor/wall. Works for any scene, atlas or flat-colour
fallback. Wall-grounding shadow strip now only applies to real walls, not objects.
- sceneModel TAVERN_LEGEND: declares over='floor' for bar/keg/shelf/table/chair/
barrel/crate/rug and over='wall' for windows.
- catSprites: the cat occupies ~40% of its 50x50 frame, so scale to ~2x the cell
and centre it — it now reads about tile-sized instead of tiny.
Client-only. Suite 57 suites / 413 tests green.
The old 'cat' used a char-goblin sprite (green monster). Replaced with actual
pixel cats from the Pet Cats Pack (CC-0, commercial-ok):
- html/assets/cats/cat1..6-idle.png — 6 cats, 10-frame 50x50 idle strips.
- catSprites.js: loads + animates a cat sprite (RK.drawCatCanvas), flips to face left.
- tileRenderer: draws entities with kind 'cat' via the cat sprite path (shadow + frame).
- tavernLife: spawns a RANDOM cat that wanders the customer floor (not behind the
bar), injected as kind 'cat' with its catSprite id. Degrades to a villager if
catSprites.js isn't loaded.
- tavern.html loads catSprites.js.
Client-only (no server change). Suite 57 suites / 413 tests green.
Used the sheet's hand-labelled roguelikeSheet_meta.json to pick the RIGHT tiles
instead of guessing:
- New tile kinds + coords (window, counter, keg/barrel, shelf, table, chair,
crate, rug, door) in sceneModel TAVERN_LEGEND/TILE_OF + assetPacks tileMap.
- tavernMap redesigned: windowed walls, a bar counter with a shelf of bottles
behind it and a keg (beer!) at each end, six tables each with a chair, a
central rug, corner barrels/crates, and a door. Chairs are walkable so standing
on one reads as sitting. Warm wood floor color (was a dark green in the fallback).
- Bartender now stands BEHIND the bar (spawn + roam confined to the row between
the top wall and the counter, sealed off by it).
Still TODO from feedback: a cat (no cat sprite on the sheet — needs an asset) and
richer bar bottles. Suite 57 suites / 413 tests green.
- The imported .tmx tavern room renders as a block-scatter in the browser (the
client falls back to the walkable grid, drawing furniture/walls as generic
blocks). Gate it behind TAVERN_DESIGNED_ROOM=true and default to the hand-built
procedural tavern (tavernMap.js) — bar + tables + walls — which renders reliably
through the theme atlas.
- Remove the tavern 'cat': it used the char-goblin sprite, so it read as a green
monster stuck in the wall. Gone until there's a real critter sprite.
A player who never customized arrives as avatar 'default' — not a char/skin/model,
so avatarVisuals resolved it to 'legacy' kind with no sprite and the renderer drew
the grey fallback circle (while NPCs with explicit char-* avatars rendered fine).
Coerce any unknown/'default' avatar to 'char-villager' on the 2D topdown path so
nobody is ever a grey dot in a room of people. iso/3d keep their own fallbacks.
Two tavern rendering bugs in Tiled mode:
1. The 'you' cell looked like a flaming dot. The warm torch light + drifting
embers are a DUNGEON effect (the player carries the torch) but _composite/
_ensureEmbers ran on every scene, so in the tavern they painted a flame on
the 'you' avatar while other players rendered normally. Gate both to
scene.isDungeon.
2. Tavern tiles showed as flat 'blocks' that never refreshed. The tile atlas
image loads async; switching INTO the tiled renderer (e.g. entering the
tavern) before it's ready paints the flat-colour fallback once and never
repaints until the next scene update. Hook atlas.onReady to re-render the
last scene the moment the atlas is ready.
Client-only; no server change. Deploy = git pull + restart (or just pull, it's
static assets).
① assetPacks.js merges the server-authoritative catalog (from the entitlement snapshot) into RK.PACKS — operator-added packs now appear client-side and existing packs pick up server definitions, retiring the client half of the split-brain. ② A confirmed direct/single_game payment now records as 'buy 1 credit + spend it': balance nets to zero but total_credits_purchased advances (recordDirectEntryPurchase), so direct play unlocks the same tier/threshold cosmetics as buying credits, and the client gets a fresh entitlement snapshot. +2 tests. Suite 49/345 green.
P1 foundation (branch feat/credits-catalog-foundation). Remaining: the buy modal reflects the unified credits model; a later pass can fully collapse single_game into the credit spend path.
Tavern: revert the per-cell 'varied floor/wall' tiles to coherent single tiles — mixing wood/stone/plank per cell read as visual noise (assetPacks.js).
Play area: draw the keyboard-focus outline inset (outline-offset -2px) so the yellow border shows on the right edge, which the container's overflow:hidden was clipping (ui.js).
Smirk: a locked wallet now shows a friendly 'unlock in the extension, then Retry' hint instead of a red error, and the LOCKED error code is preserved through connect()/signMessage(). Current Smirk builds auto-open the unlock popup on connect(); older builds throw LOCKED, which the Retry button re-invokes (smirkAuth.js).
The skin/hair/cloth-base/gear palette-ramp recoloring on the top-down Tiles character sprites looked bad, so it's disabled: tintedTileCanvas() now returns null, making every caller (drawCharTileCanvas, charTileTexture, in-game + customizer preview + Pixi) fall through to the native Kenney sprite art.
The customizer's top-down editor drops the Cloth/Skin/Hair and per-gear color rows and keeps only equipment PIECE selection (body/head/shield/weapon). The ASCII '@' color picker is unchanged (shown in the ascii/color branch, driven by colors.base via avatarVisuals.tintColorFor). Iso skin tint and premium generated-skins are untouched. Suite 46/317 green.
- tavernLife.js: client-side NPCs (bartender, patrons, cat) with autonomous
movement, random speech bubbles, and real-player chat bubbles
- T2b: spectator camera — watch live dungeon runs from inside the tavern
(game list, spectate_start/update, ESC to leave)
- R1: sceneFromGameState adapter — converts dungeon game state to render-kit
Scene with lighting grid and entity mapping
- R2: pre-populated Kenney roguelikeSheet tile defaults for the tiled renderer
- Dungeon renderer support: TileRenderer + AsciiRenderer handle lightGrid,
dungeon features/items/monster entities
- 3 new tests (242 total, 36 suites, all passing)