diff --git a/html/js/render/assetPacks.js b/html/js/render/assetPacks.js
index e1443d3..8d0b345 100644
--- a/html/js/render/assetPacks.js
+++ b/html/js/render/assetPacks.js
@@ -141,17 +141,25 @@
// keep one coherent tile per kind. (TileRenderer still accepts an array here if a
// future theme wants subtle, same-material variation.)
tiles: {
- floor: [5, 2], // tile 119 — wood floor (uniform)
- floor2: [6, 2], // tile 120 — stone floor (accent)
- wall: [13, 12], // tile 697 — stone masonry
-
- bar: [28, 0], // tile 28 — counter
- table: [20, 3], // tile 191 — table
+ floor: [5, 2], // 119 — wood floor
+ floor2: [6, 2], // 120 — stone floor (accent)
+ wall: [13, 12], // 697 — stone masonry
+ // Tavern furnishings (coords from roguelikeSheet_meta.json labels: index -> [i%57, i/57]).
+ window: [42, 2], // 156 — window (in the wall)
+ bar: [29, 0], // 29 — bar counter
+ keg: [23, 0], // 23 — barrel / beer keg (behind the bar)
+ shelf: [30, 1], // 87 — shelf of bottles (behind the bar)
+ table: [27, 5], // 312 — table
+ chair: [19, 3], // 190 — chair
+ barrel: [23, 0], // 23 — barrel (decor)
+ crate: [25, 0], // 25 — crate (decor)
+ rug: [11, 16], // 923 — rug
+ door: [37, 9], // 550 — door
// Dungeon features drawn as real tiles instead of monospace glyphs.
- entrance: [37, 9], // tile 550 — door
- exit: [37, 9], // tile 550 — door
- treasure: [42, 15], // tile 897 — item
- torch: [50, 10] // tile 620 — rubble / prop
+ entrance: [37, 9], // 550 — door
+ exit: [37, 9], // 550 — door
+ treasure: [42, 15], // 897 — item
+ torch: [50, 10] // 620 — rubble / prop
}
}
};
diff --git a/html/js/render/sceneModel.js b/html/js/render/sceneModel.js
index 78b2205..af0eee6 100644
--- a/html/js/render/sceneModel.js
+++ b/html/js/render/sceneModel.js
@@ -8,14 +8,26 @@
// Tile kinds and how each renderer treats them (glyph for ASCII, colour for tiled/fancy).
var TAVERN_LEGEND = {
- wall: { char: '#', color: '#20262e', solid: true },
- floor: { char: '·', color: '#0c1410', solid: false },
- bar: { char: '=', color: '#5a4632', solid: true },
- table: { char: 'o', color: '#4a3a28', solid: true }
+ wall: { char: '#', color: '#3a3f4b', solid: true },
+ window: { char: 'W', color: '#4a6a8a', solid: true },
+ floor: { char: '·', color: '#6b4d33', solid: false }, // warm wood, not the old dark green
+ rug: { char: '=', color: '#7a3b39', solid: false },
+ bar: { char: '=', color: '#6a4a2a', solid: true },
+ keg: { char: 'k', color: '#5a4028', solid: true },
+ shelf: { char: 'h', color: '#4a3a28', solid: true },
+ table: { char: 'T', color: '#6a4a2a', solid: true },
+ chair: { char: 'c', color: '#5a4028', solid: false }, // walkable — stand on it to "sit"
+ barrel: { char: 'B', color: '#5a4028', solid: true },
+ crate: { char: 'C', color: '#7a5a38', solid: true },
+ door: { char: 'D', color: '#3fb950', solid: false }
};
// Layout character -> tile kind.
- var TAVERN_TILE_OF = { '#': 'wall', '.': 'floor', '@': 'floor', '=': 'bar', 'o': 'table' };
+ var TAVERN_TILE_OF = {
+ '#': 'wall', 'W': 'window', '.': 'floor', '@': 'floor', 'r': 'rug',
+ '=': 'bar', 'k': 'keg', 'h': 'shelf', 'T': 'table', 'c': 'chair',
+ 'B': 'barrel', 'C': 'crate', 'D': 'door'
+ };
var AVATAR_COLORS = { 'default': '#9aa4b2', green: '#3fb950', amber: '#d29922', red: '#f85149' };
diff --git a/html/js/render/tavernLife.js b/html/js/render/tavernLife.js
index dad2ea4..7b09adb 100644
--- a/html/js/render/tavernLife.js
+++ b/html/js/render/tavernLife.js
@@ -69,8 +69,9 @@
}
function bartendersArea(cols, rows) {
- // Bartender roams along the top interior (bar counter row ~ y=2).
- return { minY: 1, maxY: 3, minX: 2, maxX: Math.max(5, cols - 3) };
+ // Behind the bar: the row between the top wall and the counter (y=1). Sealed off by the
+ // counter, so the bartender stays back there and never wanders onto the customer floor.
+ return { minY: 1, maxY: 1, minX: 3, maxX: Math.max(5, cols - 5) };
}
function patronsArea(cols, rows) {
// Patrons roam the middle/lower floor.
@@ -195,8 +196,8 @@
return { x: 1, y: 1 };
}
- // Bartender near the bar (top area).
- var barSpot = findFloor(Math.floor(cols / 2), 3);
+ // Bartender BEHIND the bar (row 1, between the top wall and the counter).
+ var barSpot = findFloor(Math.floor(cols / 2), 1);
var bartender = makeNPC('bartender', 'bartender', 'char-merchant', barSpot.x, barSpot.y, {
label: 'Bartender', color: '#c9a25e', role: 'bartender',
roamArea: bartendersArea(cols, rows)
diff --git a/src/multiplayer/tavernMap.js b/src/multiplayer/tavernMap.js
index be6a0c1..a56172b 100644
--- a/src/multiplayer/tavernMap.js
+++ b/src/multiplayer/tavernMap.js
@@ -1,26 +1,22 @@
/**
- * Default Tavern layout — a small room with hand-placed props (bar, tables), generated
- * procedurally so every row is guaranteed the same width.
+ * Default Tavern layout — a hand-built room rendered through the theme atlas. Every row is the same
+ * width (ragged ASCII maps are a classic off-by-one collision bug), and the legend below drives both
+ * the client tiles (sceneModel TAVERN_LEGEND/TILE_OF) and Room walkability.
*
* Legend:
- * '#' wall '=' bar counter 'o' table / stool (all solid)
- * '.' floor '@' spawn point (walkable)
+ * '#' wall 'W' window '=' bar counter 'k' keg (barrel) 'h' shelf of bottles
+ * 'T' table 'c' chair 'B' barrel 'C' crate 'D' door
+ * '.' floor 'r' rug '@' spawn
*
- * Anything NOT in FLOOR_CHARS is treated as solid by the Room engine. The layout is
- * generated procedurally so every row is guaranteed the same width (ragged ASCII maps
- * are a classic source of off-by-one collision bugs).
+ * Walkable = FLOOR_CHARS ('.', '@', 'r' rug, 'c' chair, 'D' door). Everything else is solid, so
+ * players weave between the furniture. The bartender stands BEHIND the bar (the row between the top
+ * wall and the counter).
*/
-// Characters an Occupant may stand on.
-const FLOOR_CHARS = new Set(['.', '@']);
+// Characters an Occupant may stand on. Chairs are walkable so standing on one reads as "sitting".
+const FLOOR_CHARS = new Set(['.', '@', 'r', 'c', 'D']);
-/**
- * Build the default tavern as an array of equal-length strings.
- * @param {number} cols
- * @param {number} rows
- * @returns {string[]}
- */
-function buildDefaultTavern(cols = 26, rows = 13) {
+function buildDefaultTavern(cols = 28, rows = 15) {
const grid = [];
for (let y = 0; y < rows; y++) {
const row = new Array(cols);
@@ -30,25 +26,36 @@ function buildDefaultTavern(cols = 26, rows = 13) {
}
grid.push(row);
}
+ // set: overwrite anything (incl. walls, for windows/door). put: don't clobber walls.
+ const set = (x, y, ch) => { if (grid[y] && x >= 0 && x < cols) grid[y][x] = ch; };
+ const put = (x, y, ch) => { if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== '#') grid[y][x] = ch; };
- const put = (x, y, ch) => {
- if (grid[y] && grid[y][x] !== undefined && grid[y][x] !== '#') grid[y][x] = ch;
- };
+ // Windows punched into the top wall and the two side walls.
+ for (let x = 3; x < cols - 3; x += 4) set(x, 0, 'W');
+ for (let y = 4; y < rows - 3; y += 4) { set(0, y, 'W'); set(cols - 1, y, 'W'); }
- // Bar counter along the top interior wall.
- for (let x = 3; x <= 16; x++) put(x, 2, '=');
+ // The bar: a counter across the top interior (row 2), a shelf of bottles behind it (row 1),
+ // and a keg (barrel) bracketing each end. Row 1 is the bartender's space, sealed off by the
+ // counter — players can't get behind it.
+ const barY = 2, barX0 = 3, barX1 = cols - 5;
+ for (let x = barX0; x <= barX1; x++) set(x, barY, '=');
+ for (let x = barX0; x <= barX1; x += 2) set(x, barY - 1, 'h'); // bottles behind the bar
+ set(barX0 - 1, barY, 'k'); set(barX1 + 1, barY, 'k'); // kegs bracket the counter
- // Table + stool clusters (2x2 blocks of 'o').
- const tables = [[5, 5], [12, 5], [18, 5], [5, 9], [12, 9], [18, 9]];
- for (const [tx, ty] of tables) {
- put(tx, ty, 'o'); put(tx + 1, ty, 'o');
- put(tx, ty + 1, 'o'); put(tx + 1, ty + 1, 'o');
- }
+ // Tables (each with a chair below it), spread across the main floor with walkable aisles.
+ const tables = [[5, 6], [12, 6], [19, 6], [5, 10], [12, 10], [19, 10]];
+ for (const [tx, ty] of tables) { put(tx, ty, 'T'); put(tx, ty + 1, 'c'); }
- // Spawn pad near the entrance (bottom-center). Two tiles so early tests have a
- // deterministic adjacent pair to exercise occupant-collision rules.
- put(12, 11, '@');
- put(13, 11, '@');
+ // A rug in the centre of the room.
+ for (let y = 8; y <= 9; y++) for (let x = 11; x <= 15; x++) put(x, y, 'r');
+
+ // Corner decor — barrels and crates.
+ put(1, 1, 'B'); put(cols - 2, 1, 'C'); put(1, rows - 2, 'C'); put(cols - 2, rows - 2, 'B');
+
+ // Door + spawn pad at the bottom-centre (two spawn tiles for deterministic adjacency tests).
+ const mid = Math.floor(cols / 2);
+ set(mid, rows - 1, 'D');
+ put(mid, rows - 2, '@'); put(mid - 1, rows - 2, '@');
return grid.map(r => r.join(''));
}
diff --git a/test/renderScene.test.js b/test/renderScene.test.js
index 586d447..f8083b7 100644
--- a/test/renderScene.test.js
+++ b/test/renderScene.test.js
@@ -9,7 +9,7 @@ describe('sceneFromTavern (render kit adapter)', () => {
};
const state = {
cols: 3, rows: 2,
- layout: ['#=@', 'o..'],
+ layout: ['#=@', 'T..'],
occupants: [{ id: 'a', x: 2, y: 0, avatar: 'green', appearance, facing: 'left', name: 'Al' }]
};
const s = scene.sceneFromTavern(state, 'a');