@dreadwood-types/types 0.1.44 → 0.1.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/areaTransitions.d.ts +1 -1
  2. package/dist/areaTransitions.d.ts.map +1 -1
  3. package/dist/areaTransitions.js +28 -0
  4. package/dist/areaTransitions.js.map +1 -1
  5. package/dist/bank.d.ts +2 -1
  6. package/dist/bank.d.ts.map +1 -1
  7. package/dist/bank.js +2 -1
  8. package/dist/bank.js.map +1 -1
  9. package/dist/blacksmithing.d.ts +16 -1
  10. package/dist/blacksmithing.d.ts.map +1 -1
  11. package/dist/blacksmithing.js +221 -0
  12. package/dist/blacksmithing.js.map +1 -1
  13. package/dist/bloodRiver.d.ts +115 -0
  14. package/dist/bloodRiver.d.ts.map +1 -0
  15. package/dist/bloodRiver.js +252 -0
  16. package/dist/bloodRiver.js.map +1 -0
  17. package/dist/brokenRoad.d.ts +103 -0
  18. package/dist/brokenRoad.d.ts.map +1 -0
  19. package/dist/brokenRoad.js +167 -0
  20. package/dist/brokenRoad.js.map +1 -0
  21. package/dist/capital.d.ts +270 -0
  22. package/dist/capital.d.ts.map +1 -0
  23. package/dist/capital.js +306 -0
  24. package/dist/capital.js.map +1 -0
  25. package/dist/clientConfig.d.ts +34 -2
  26. package/dist/clientConfig.d.ts.map +1 -1
  27. package/dist/clientConfig.js +26 -2
  28. package/dist/clientConfig.js.map +1 -1
  29. package/dist/constants.d.ts +145 -9
  30. package/dist/constants.d.ts.map +1 -1
  31. package/dist/constants.js +302 -8
  32. package/dist/constants.js.map +1 -1
  33. package/dist/cooking.d.ts +21 -0
  34. package/dist/cooking.d.ts.map +1 -1
  35. package/dist/cooking.js +52 -26
  36. package/dist/cooking.js.map +1 -1
  37. package/dist/dreadwoodsZones.d.ts +15 -1
  38. package/dist/dreadwoodsZones.d.ts.map +1 -1
  39. package/dist/dreadwoodsZones.js +63 -0
  40. package/dist/dreadwoodsZones.js.map +1 -1
  41. package/dist/equipment.d.ts +16 -1
  42. package/dist/equipment.d.ts.map +1 -1
  43. package/dist/equipment.js +61 -0
  44. package/dist/equipment.js.map +1 -1
  45. package/dist/groundItems.d.ts +5 -0
  46. package/dist/groundItems.d.ts.map +1 -1
  47. package/dist/groundItems.js +12 -1
  48. package/dist/groundItems.js.map +1 -1
  49. package/dist/index.d.ts +5 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +5 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/landmarks.d.ts +600 -104
  54. package/dist/landmarks.d.ts.map +1 -1
  55. package/dist/landmarks.js +803 -105
  56. package/dist/landmarks.js.map +1 -1
  57. package/dist/messages.d.ts +146 -11
  58. package/dist/messages.d.ts.map +1 -1
  59. package/dist/messages.js +4 -0
  60. package/dist/messages.js.map +1 -1
  61. package/dist/mineCave.d.ts +71 -0
  62. package/dist/mineCave.d.ts.map +1 -0
  63. package/dist/mineCave.js +39 -0
  64. package/dist/mineCave.js.map +1 -0
  65. package/dist/mining.d.ts +16 -5
  66. package/dist/mining.d.ts.map +1 -1
  67. package/dist/mining.js +24 -5
  68. package/dist/mining.js.map +1 -1
  69. package/dist/quests.d.ts +3 -1
  70. package/dist/quests.d.ts.map +1 -1
  71. package/dist/quests.js +18 -1
  72. package/dist/quests.js.map +1 -1
  73. package/dist/travel.d.ts +66 -0
  74. package/dist/travel.d.ts.map +1 -0
  75. package/dist/travel.js +91 -0
  76. package/dist/travel.js.map +1 -0
  77. package/dist/woodwork.d.ts +128 -13
  78. package/dist/woodwork.d.ts.map +1 -1
  79. package/dist/woodwork.js +253 -19
  80. package/dist/woodwork.js.map +1 -1
  81. package/dist/xp.d.ts +7 -0
  82. package/dist/xp.d.ts.map +1 -1
  83. package/dist/xp.js +7 -0
  84. package/dist/xp.js.map +1 -1
  85. package/package.json +1 -1
package/dist/landmarks.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { BEACH_GATE_X, BEACH_SAND_START_Z, BEACH_START_Z, CHUNK_SIZE, OCEAN_START_Z, STONE_HOUSE_LODGE_RADIUS_ACROSS, STONE_HOUSE_RADIUS, UNDERGROUND_ORIGIN_X, WORLD_NORTH_PAD, WORLD_WIDTH, toLocalZ, toWorldZ, } from './constants.js';
2
- import { DEADLANDS_DEFENSE_DEPTH, DEADLANDS_GATE_X, DEADLANDS_MAX_X, DEADLANDS_MAX_Z, DEADLANDS_MIN_X, } from './zones.js';
2
+ import { DEADLANDS_DEFENSE_DEPTH, DEADLANDS_GATE_X, DEADLANDS_MAX_X, DEADLANDS_MAX_Z, DEADLANDS_MIN_X, DEADLANDS_MIN_Z, } from './zones.js';
3
3
  import { SNOW_DEEP_Z, SNOW_EDGE_Z } from './snow.js';
4
4
  import { dreadwoodsNpcZone } from './dreadwoodsZones.js';
5
+ import { CAPITAL_DISTRICTS, CAPITAL_KEEP, CAPITAL_WORKS } from './capital.js';
6
+ import { BLOOD_LAKE_CX, BLOOD_LAKE_CZ, DREADWOODS_WEST_GATE_HALF_W, DREADWOODS_WEST_GATE_Z, } from './bloodRiver.js';
5
7
  export const CROSSROADS_WORLD_X = BEACH_GATE_X;
6
8
  export const CROSSROADS_LOCAL_Z = 17;
7
9
  /**
@@ -45,6 +47,157 @@ export const HOPE_PIER_BRANCH_Z = OCEAN_START_Z + 14;
45
47
  export const HOPE_PIER_BRANCH_HALF_W = 1;
46
48
  export const HOPE_PIER_BRANCH_MIN_X = HOPE_PIER_X + HOPE_PIER_HALF_W + 1;
47
49
  export const HOPE_PIER_BRANCH_MAX_X = HOPE_PIER_X + 12;
50
+ /**
51
+ * The last four columns of the branch open out into a five-wide landing
52
+ * stage against the ship: somewhere for cargo, nets and the ship's master to
53
+ * stand without blocking the walk out.
54
+ */
55
+ export const HOPE_PIER_BRANCH_WIDE_MIN_X = HOPE_PIER_BRANCH_MAX_X - 3;
56
+ export const HOPE_PIER_BRANCH_WIDE_HALF_W = 2;
57
+ /**
58
+ * Branson, master of the moored ship, stands on the landing stage facing back
59
+ * down the branch so he is the first thing a player walking out to the ship
60
+ * meets. His landing tile (where a voyage puts you ashore here) is beside him.
61
+ */
62
+ export const BRANSON_SPAWN_X = HOPE_PIER_BRANCH_MAX_X - 1;
63
+ export const BRANSON_SPAWN_Z = HOPE_PIER_BRANCH_Z;
64
+ export const HOPE_BEACH_LANDING = { x: HOPE_PIER_BRANCH_MAX_X - 2, z: HOPE_PIER_BRANCH_Z + 1 };
65
+ /**
66
+ * BRANSON'S LANDING
67
+ *
68
+ * Branson's port on the Frozen Shore: a bay cut into the west crags level
69
+ * with the Frozen Lake, a snow-dusted plank pier running nineteen tiles out
70
+ * over the water to a landing head, his mate Oskar on the shore beside the
71
+ * sailings board, the ship moored along the head's south side, and a hut up
72
+ * on the rocky shelf north of the pier foot. It used to be a placeholder
73
+ * jetty on the crag line; a nine-tile cove and a seven-tile pier replaced it
74
+ * on 2026-09-17, and the same evening James found that cramped (the board
75
+ * readable from one side, everything on top of everything else), so the
76
+ * bay was opened up to twenty tiles, the pier lengthened out into it and
77
+ * the shore spread out, with the Frozen Lake moved east to make the room
78
+ * (see backend bransonsLanding.ts).
79
+ *
80
+ * World tiles (the north pad is part of the world grid, so no toWorldZ).
81
+ * FROZEN_LAKE_CENTER is declared further down; its z is 236.
82
+ */
83
+ /** The two rows of the pier walk, and the columns it runs over. */
84
+ export const BRANSONS_LANDING_PIER_MIN_Z = 236;
85
+ export const BRANSONS_LANDING_PIER_MAX_Z = 237;
86
+ /** The seaward end of the walk (the head's west column) and its foot on the shore. */
87
+ export const BRANSONS_LANDING_PIER_MIN_X = 3;
88
+ export const BRANSONS_LANDING_PIER_FOOT_X = 21;
89
+ /** The 4x4 landing head at the seaward end, a row wider than the walk each side. */
90
+ export const BRANSONS_LANDING_HEAD_MAX_X = 6;
91
+ export const BRANSONS_LANDING_HEAD_MIN_Z = 235;
92
+ export const BRANSONS_LANDING_HEAD_MAX_Z = 238;
93
+ /** Where the gangplank leaves the head for the ship: the head's south row. */
94
+ export const BRANSONS_LANDING_GANGPLANK = { x: 4, z: BRANSONS_LANDING_HEAD_MAX_Z };
95
+ /**
96
+ * The ship, lying east-west along the head's south side with its bow to the
97
+ * open sea. `face270` turns the SailShip's +z bow onto -x; the `landingship`
98
+ * token picks the shorter hull.
99
+ */
100
+ export const BRANSONS_LANDING_SHIP = { x: 6, z: 241 };
101
+ export const BRANSONS_LANDING_SHIP_STYLE = 'landingship-face270';
102
+ /**
103
+ * The hull's tile footprint (all bay water), blocked so nothing paths under
104
+ * it. Eleven long and four wide about the anchor: the bow a tile in from the
105
+ * world's edge, the stern under the walk.
106
+ */
107
+ export const BRANSONS_LANDING_SHIP_HULL = { minX: 1, maxX: 11, minZ: 239, maxZ: 243 };
108
+ /**
109
+ * Bay water by row: the columns from the rim water (x 0-1) east that the
110
+ * bay opens up. Rounded at both ends, twenty wide along the pier.
111
+ */
112
+ export const BRANSONS_LANDING_COVE_ROWS = [
113
+ { z: 224, maxX: 3 },
114
+ { z: 225, maxX: 6 },
115
+ { z: 226, maxX: 9 },
116
+ { z: 227, maxX: 12 },
117
+ { z: 228, maxX: 14 },
118
+ { z: 229, maxX: 16 },
119
+ { z: 230, maxX: 18 },
120
+ { z: 231, maxX: 19 },
121
+ { z: 232, maxX: 20 },
122
+ { z: 233, maxX: 20 },
123
+ { z: 234, maxX: 20 },
124
+ { z: 235, maxX: 20 },
125
+ { z: 236, maxX: 20 },
126
+ { z: 237, maxX: 20 },
127
+ { z: 238, maxX: 20 },
128
+ { z: 239, maxX: 20 },
129
+ { z: 240, maxX: 20 },
130
+ { z: 241, maxX: 20 },
131
+ { z: 242, maxX: 20 },
132
+ { z: 243, maxX: 20 },
133
+ { z: 244, maxX: 19 },
134
+ { z: 245, maxX: 18 },
135
+ { z: 246, maxX: 16 },
136
+ { z: 247, maxX: 14 },
137
+ { z: 248, maxX: 12 },
138
+ { z: 249, maxX: 9 },
139
+ { z: 250, maxX: 6 },
140
+ { z: 251, maxX: 3 },
141
+ ];
142
+ /** The rim rows the bay replaces: the west rim loop places no crag on these. */
143
+ export const BRANSONS_LANDING_COVE_MIN_Z = 224;
144
+ export const BRANSONS_LANDING_COVE_MAX_Z = 251;
145
+ /** Everything the landing owns, for the scenery sweep and the discovery zone. */
146
+ export const BRANSONS_LANDING_WORKS = { minX: 0, maxX: 34, minZ: 220, maxZ: 255 };
147
+ /** Oskar, Branson's mate, on the shore a step off the pier foot, facing the boards. */
148
+ export const BRANSONS_LANDING_OSKAR = { x: 22, z: 235, faceX: 21, faceZ: 235 };
149
+ /** The sailings board, up the apron from Oskar with room to walk round it. */
150
+ export const BRANSONS_LANDING_BOARD = { x: 26, z: 233 };
151
+ /** The hut on the shelf north of the pier foot, its door to the south, and the steps down. */
152
+ export const BRANSONS_LANDING_HUT = { x: 27, z: 228 };
153
+ export const BRANSONS_LANDING_HUT_LANTERN = { x: 28, z: 230 };
154
+ export const BRANSONS_LANDING_STEPS = [
155
+ { x: 27, z: 230 },
156
+ { x: 27, z: 231 },
157
+ ];
158
+ /** The shelf the hut stands on, its ledge row, and the apron the pier leaves from. */
159
+ export const BRANSONS_LANDING_SHELF_HEIGHT = 3;
160
+ export const BRANSONS_LANDING_LEDGE_Z = 230;
161
+ export const BRANSONS_LANDING_APRON = { minX: 21, maxX: 33, minZ: 231, maxZ: 243 };
162
+ export const BRANSONS_LANDING_APRON_HEIGHT = 1;
163
+ /** A lantern post on the apron, south of the walk, and the stores stacked by the board. */
164
+ export const BRANSONS_LANDING_APRON_LANTERN = { x: 24, z: 240 };
165
+ /** Ice floes on the bay water, clear of the ship and the pier. */
166
+ export const BRANSONS_LANDING_FLOES = [
167
+ { x: 2, z: 227, scale: 0.9 },
168
+ { x: 10, z: 229, scale: 0.8 },
169
+ { x: 16, z: 231, scale: 1.1 },
170
+ { x: 2, z: 233, scale: 1.0 },
171
+ { x: 19, z: 234, scale: 0.8 },
172
+ { x: 15, z: 240, scale: 0.9 },
173
+ { x: 18, z: 245, scale: 1.0 },
174
+ { x: 10, z: 248, scale: 0.9 },
175
+ { x: 4, z: 249, scale: 1.1 },
176
+ ];
177
+ /** True for a cove water tile (the rim water at x 0-1 is water on every row anyway). */
178
+ export function isBransonsLandingCoveTile(x, z) {
179
+ if (x < 0)
180
+ return false;
181
+ const row = BRANSONS_LANDING_COVE_ROWS.find((r) => r.z === z);
182
+ return !!row && x <= row.maxX;
183
+ }
184
+ /** True for a plank of the pier: the two-row walk out from the foot, and the head. */
185
+ export function isBransonsLandingDeckTile(x, z) {
186
+ if (x >= BRANSONS_LANDING_PIER_MIN_X &&
187
+ x <= BRANSONS_LANDING_HEAD_MAX_X &&
188
+ z >= BRANSONS_LANDING_HEAD_MIN_Z &&
189
+ z <= BRANSONS_LANDING_HEAD_MAX_Z) {
190
+ return true;
191
+ }
192
+ return (x >= BRANSONS_LANDING_PIER_MIN_X &&
193
+ x <= BRANSONS_LANDING_PIER_FOOT_X &&
194
+ z >= BRANSONS_LANDING_PIER_MIN_Z &&
195
+ z <= BRANSONS_LANDING_PIER_MAX_Z);
196
+ }
197
+ /** Kept for the rim loop and older callers: the crag line the bay now cuts through. */
198
+ export const FROZEN_SHORE_JETTY = { x: 2, z: 237 };
199
+ /** Where a voyage puts you ashore: the pier's foot plank, on the landing side. */
200
+ export const FROZEN_SHORE_LANDING = { x: BRANSONS_LANDING_PIER_FOOT_X, z: BRANSONS_LANDING_PIER_MAX_Z };
48
201
  /**
49
202
  * First smashed span, about a third of the way out - far enough that the walk
50
203
  * to it is a walk, close enough to see the break from the sand.
@@ -67,6 +220,44 @@ export const HOPE_PIER_WIDE_HALF_W = 2;
67
220
  /** The moored ship, off the seaward corner of the branch. */
68
221
  export const HOPE_PIER_SHIP_X = HOPE_PIER_BRANCH_MAX_X + 4;
69
222
  export const HOPE_PIER_SHIP_Z = HOPE_PIER_BRANCH_Z;
223
+ /**
224
+ * The west spur: the smelt stage and its storage hut.
225
+ *
226
+ * It leaves the MIDDLE deck, not the shore-side walk, because the smelt water
227
+ * is here, past the first smashed span, and the bank the hut opens is meant to
228
+ * share that gate: plank gap 1 and both the fishing and the strongbox are
229
+ * yours. It runs WEST because the ship already has the east, and two spurs off
230
+ * one side would read as a second landing stage rather than a fishing stage.
231
+ *
232
+ * The spur is exactly the middle deck's three rows, so it reads as the deck
233
+ * continuing sideways rather than a corridor bolted onto it. The hut block
234
+ * hangs off the spur's south side and its door opens north onto the spur's
235
+ * south row; the hut's own 7x7 stands on decking laid the same way, so nothing
236
+ * inside it is ever open sea.
237
+ */
238
+ export const HOPE_PIER_SPUR_Z = HOPE_PIER_MID_MIN_Z + 1;
239
+ export const HOPE_PIER_SPUR_HALF_W = 1;
240
+ export const HOPE_PIER_SPUR_MAX_X = HOPE_PIER_X - HOPE_PIER_WIDE_HALF_W - 1;
241
+ export const HOPE_PIER_SPUR_MIN_X = HOPE_PIER_X - 12;
242
+ export const HOPE_PIER_HUT_X = HOPE_PIER_SPUR_MIN_X + 3;
243
+ export const HOPE_PIER_HUT_Z = HOPE_PIER_SPUR_Z + HOPE_PIER_SPUR_HALF_W + 4;
244
+ /**
245
+ * 3.5 lands in the radius-3 footprint bracket (see buildingScaleBracketRadius):
246
+ * a 7x7 wall ring, two tiles smaller each way than Base Camp's, because a
247
+ * 9x9 hut would be wider than the stage it stands on.
248
+ */
249
+ export const HOPE_PIER_HUT_SCALE = 3.5;
250
+ export const HOPE_PIER_HUT_RADIUS = 3;
251
+ /** The strongbox stands two rows behind the hut centre, as at Base Camp. */
252
+ export const HOPE_PIER_HUT_CHEST_DZ = 2;
253
+ /**
254
+ * The pier banker stands one row behind the centre: behind the counter (which
255
+ * straddles the seam between the centre row and his), in front of the chest.
256
+ * The centre row itself is where the door portal drops a player, so nothing
257
+ * may stand on it.
258
+ */
259
+ export const HOPE_PIER_BANKER_DZ = 1;
260
+ export const HOPE_PIER_BANKER_NAME = 'Bursar Finch';
70
261
  /**
71
262
  * Saul's tackle pitch: on the sand beside the pier ramp, west of the lane so
72
263
  * he never stands in the way of the walk out. He faces the pier.
@@ -79,17 +270,57 @@ export const SAUL_SPAWN_X = HOPE_PIER_X - HOPE_PIER_HALF_W - 2;
79
270
  */
80
271
  export const SAUL_SPAWN_Z = HOPE_PIER_SHORE_Z - 3;
81
272
  /**
82
- * Smelt water beside the Long Pier: past the first smashed span and short of
83
- * the sea bass, which hold past the second. Two off the middle deck, one off
84
- * the walk either side of it. Each sits two tiles from the nearest plank, the
85
- * ordinary fishing range, so the cast is made from the deck.
86
- */
87
- export const HOPE_PIER_SMELT_SPOTS = [
88
- { x: HOPE_PIER_X - 4, z: OCEAN_START_Z + 43 },
89
- { x: HOPE_PIER_X + 4, z: OCEAN_START_Z + 43 },
90
- { x: HOPE_PIER_X - 3, z: OCEAN_START_Z + 36 },
91
- { x: HOPE_PIER_X + 3, z: OCEAN_START_Z + 50 },
92
- ];
273
+ * The pier's fish, smelt markers and sea bass alike, hold PIER_FISH_MIN_OFF_DECK
274
+ * to PIER_CAST_MAX_TILES off the nearest plank (pierDeckDistance: the walk,
275
+ * the head, the decks, the branch, the spur and the hut block all count), and
276
+ * a cast goes straight to the fish. The old model anchored the smelt markers
277
+ * two tiles off the planks and sent the line to a random tile out from the
278
+ * angler, so a click on one marker cast somewhere else entirely (James: "it
279
+ * casted to a completely different location"). Now the marker IS the fish
280
+ * and the line lands on it.
281
+ *
282
+ * Four, not three: three left two tiles of water between the rail and the
283
+ * fish, which from the game camera still read as a fish beside the pier.
284
+ * PIER_CAST_MAX_TILES is also how far an angler may stand from the fish.
285
+ */
286
+ export const PIER_FISH_MIN_OFF_DECK = 4;
287
+ export const PIER_CAST_MAX_TILES = 10;
288
+ /**
289
+ * Smelt markers out in the pier's smelt band at once, each on a tile the
290
+ * backend draws at random (collectPierSmeltTiles) and redraws on the lake
291
+ * timer and after every catch.
292
+ */
293
+ export const HOPE_PIER_SMELT_SPOT_COUNT = 5;
294
+ /**
295
+ * Chebyshev tiles around HOPE_PIER_SHIP_X/Z no pier fish may sit in. The
296
+ * moored ship has a one-tile footprint but its hull mesh covers a few tiles
297
+ * each way, so a smelt marker or a bass inside this square would be on deck.
298
+ */
299
+ export const HOPE_PIER_SHIP_KEEP_OUT = 4;
300
+ /**
301
+ * Chebyshev distance from (x, z) to the nearest lane tile: the walk, the head,
302
+ * the middle deck, the east branch and the west spur with its hut block. A
303
+ * bounded ring scan over onHopePierLane rather than a closed form, so it stays
304
+ * right when the next spur is added. Returns 0 on the lane; capped at `limit`
305
+ * (returned as limit + 1 when nothing is nearer), which keeps the scan cheap
306
+ * for the callers that only care about the fish band of a dozen tiles.
307
+ */
308
+ export function pierDeckDistance(x, z, limit = PIER_CAST_MAX_TILES) {
309
+ if (onHopePierLane(x, z))
310
+ return 0;
311
+ for (let r = 1; r <= limit; r++) {
312
+ for (let dz = -r; dz <= r; dz++) {
313
+ const edgeRow = Math.abs(dz) === r;
314
+ // Only the ring at radius r: interior tiles were covered by a smaller r.
315
+ const step = edgeRow ? 1 : 2 * r;
316
+ for (let dx = -r; dx <= r; dx += step) {
317
+ if (onHopePierLane(x + dx, z + dz))
318
+ return r;
319
+ }
320
+ }
321
+ }
322
+ return limit + 1;
323
+ }
93
324
  /** Tiles either side of the centre line at this row. Wider over the two decks. */
94
325
  export function hopePierHalfWidthAt(z) {
95
326
  if (z >= HOPE_PIER_HEAD_MIN_Z)
@@ -98,15 +329,36 @@ export function hopePierHalfWidthAt(z) {
98
329
  return HOPE_PIER_WIDE_HALF_W;
99
330
  return HOPE_PIER_HALF_W;
100
331
  }
332
+ /** Tiles either side of the branch's centre row at this column. Wider over the landing stage. */
333
+ export function hopePierBranchHalfWidthAt(x) {
334
+ return x >= HOPE_PIER_BRANCH_WIDE_MIN_X ? HOPE_PIER_BRANCH_WIDE_HALF_W : HOPE_PIER_BRANCH_HALF_W;
335
+ }
101
336
  /** The east spur out to the moored ship. */
102
337
  export function onHopePierBranch(x, z) {
103
- return (Math.abs(z - HOPE_PIER_BRANCH_Z) <= HOPE_PIER_BRANCH_HALF_W &&
104
- x >= HOPE_PIER_BRANCH_MIN_X &&
105
- x <= HOPE_PIER_BRANCH_MAX_X);
338
+ return (x >= HOPE_PIER_BRANCH_MIN_X &&
339
+ x <= HOPE_PIER_BRANCH_MAX_X &&
340
+ Math.abs(z - HOPE_PIER_BRANCH_Z) <= hopePierBranchHalfWidthAt(x));
341
+ }
342
+ /**
343
+ * The west spur off the middle deck, and the hut block hanging off its south
344
+ * side. The hut's 7x7 counts as lane because it is decking too: the deck
345
+ * builder rails every lane tile whose neighbour is not lane, and a rail line
346
+ * running under the hut walls would poke out through them.
347
+ */
348
+ export function onHopePierSpur(x, z) {
349
+ if (x >= HOPE_PIER_SPUR_MIN_X &&
350
+ x <= HOPE_PIER_SPUR_MAX_X &&
351
+ Math.abs(z - HOPE_PIER_SPUR_Z) <= HOPE_PIER_SPUR_HALF_W) {
352
+ return true;
353
+ }
354
+ return (Math.abs(x - HOPE_PIER_HUT_X) <= HOPE_PIER_HUT_RADIUS &&
355
+ Math.abs(z - HOPE_PIER_HUT_Z) <= HOPE_PIER_HUT_RADIUS);
106
356
  }
107
357
  export function onHopePierLane(x, z) {
108
358
  if (onHopePierBranch(x, z))
109
359
  return true;
360
+ if (onHopePierSpur(x, z))
361
+ return true;
110
362
  return (z >= HOPE_PIER_SHORE_Z &&
111
363
  z <= HOPE_PIER_END_Z &&
112
364
  Math.abs(x - HOPE_PIER_X) <= hopePierHalfWidthAt(z));
@@ -177,17 +429,55 @@ export function isBaseCampCaveMouthStyle(style) {
177
429
  /** Small lookout beyond the Base Camp tunnel, reached only after clearing Livonia. */
178
430
  export const BASECAMP_FAR_SIDE_X = BASECAMP_CAVE_X + 30;
179
431
  export const BASECAMP_FAR_SIDE_LOCAL_Z = BASECAMP_CAVE_LOCAL_Z;
432
+ /**
433
+ * How far north the whole battlefield front line sits from the farm.
434
+ *
435
+ * The south palisade used to stand one tile off No Harm Farm's own north
436
+ * fence, so a player walked out of the farm gate straight into a war. Every
437
+ * absolute Z on the pad is written as its original survey number put through
438
+ * `bfZ`, which keeps the internal layout - lake, camps, cabbage rows, the
439
+ * bridge span, the Captain's parade ground - exactly as it was designed and
440
+ * moves the whole thing as one piece.
441
+ *
442
+ * The ceiling on this number is the lake, not the farm. The water sheet runs
443
+ * on east through the corridor south of the Dreadwoods, whose own south edge
444
+ * is DEADLANDS_MAX_Z (639); the wall returns need a band between the two. At
445
+ * 18 the lake's north bank lands at 648, leaving that corridor 9 tiles deep
446
+ * with the returns still reaching it. Push much past this and the lake starts
447
+ * eating the Dreadwoods' south wall.
448
+ *
449
+ * Declared up here, ahead of the Amazon Trail and Calverton, because the two
450
+ * lake rows are the datum both of them are measured from: the trail ends on
451
+ * the south shore and the town stands back from the north shore.
452
+ */
453
+ export const BATTLEFIELD_NORTH_SHIFT = 18;
454
+ /** An original survey Z, moved north by BATTLEFIELD_NORTH_SHIFT. */
455
+ const bfZ = (surveyZ) => surveyZ - BATTLEFIELD_NORTH_SHIFT;
456
+ /**
457
+ * Lake between soldier (south / farmward) and goblin (north) camps.
458
+ * Water blocks walking; the battlefield crossing is the long bridge once repaired.
459
+ * Thick N-S band on the pad, then the same Z band continues east through the
460
+ * corridor south of the Deadlands, past the west-face enclave, until it meets
461
+ * the SE high-rock row.
462
+ */
463
+ export const BATTLEFIELD_LAKE_MIN_Z = bfZ(666);
464
+ export const BATTLEFIELD_LAKE_MAX_Z = bfZ(698);
180
465
  /**
181
466
  * The Amazon Trail - the long, tree-lined path leading north out of the Rat
182
467
  * Tunnel far-side exit (the Rat Tunnel is "the Amazonian cave": rats, then the
183
468
  * amazons of Room 3, then Livonia). It runs straight north up the eastern
184
- * margin of the world, east of the Dreadwoods, and crosses the Battlefield lake
185
- * on a raised dirt causeway - carveLakeTile already keeps a Dirt lane and its
186
- * shoulders dry where a road meets the water, so no bridge entity is needed.
469
+ * margin of the world, east of the Dreadwoods, to the Battlefield lake's
470
+ * south shore. There the road bends east along the shore to the Calverton
471
+ * bridge, which crosses on the town gate's own axis (CALVERTON_BRIDGE_X).
472
+ *
473
+ * The lane used to cross the lake itself, on a causeway and then a bridge,
474
+ * and land 45 tiles west of the gate with a road along the north bank to
475
+ * finish the job. James, standing on the gate approach: "we should come off
476
+ * the bridge here instead, it's a better location entering calverton".
187
477
  *
188
- * End to end it is AMAZON_TRAIL_LENGTH tiles, which at MOVE_SPEED_TILES_PER_SEC
189
- * (3.9) is about a 30 second walk. The far end opens into open ground kept
190
- * clear for a later zone.
478
+ * End to end the lane is AMAZON_TRAIL_LENGTH tiles, which at
479
+ * MOVE_SPEED_TILES_PER_SEC (3.9) is about a 20 second walk; the shore road,
480
+ * the bridge and the gate road add about 100 more.
191
481
  */
192
482
  export const AMAZON_TRAIL_X = BASECAMP_FAR_SIDE_X;
193
483
  export const AMAZON_TRAIL_HALF_W = 5;
@@ -195,8 +485,13 @@ export const AMAZON_TRAIL_HALF_W = 5;
195
485
  export const AMAZON_TRAIL_SHOULDER = 7;
196
486
  /** Just past the exit landing, so the path is already under your feet when you arrive. */
197
487
  export const AMAZON_TRAIL_SOUTH_WORLD_Z = toWorldZ(BASECAMP_FAR_SIDE_LOCAL_Z) + 2;
198
- export const AMAZON_TRAIL_LENGTH = 118;
199
- export const AMAZON_TRAIL_NORTH_WORLD_Z = AMAZON_TRAIL_SOUTH_WORLD_Z - AMAZON_TRAIL_LENGTH;
488
+ /**
489
+ * The lane's last row: the north row of the shore road, one row up the bank
490
+ * from the lake's south shore strip. The two overlap there, which is what
491
+ * makes the join read as one road bending rather than two roads meeting.
492
+ */
493
+ export const AMAZON_TRAIL_NORTH_WORLD_Z = BATTLEFIELD_LAKE_MAX_Z + 2;
494
+ export const AMAZON_TRAIL_LENGTH = AMAZON_TRAIL_SOUTH_WORLD_Z - AMAZON_TRAIL_NORTH_WORLD_Z;
200
495
  /** True for the walkable lane itself (not the flanking tree bands). */
201
496
  export function inAmazonTrail(x, z) {
202
497
  return (x >= AMAZON_TRAIL_X - AMAZON_TRAIL_HALF_W &&
@@ -226,8 +521,9 @@ export function inAmazonTrail(x, z) {
226
521
  * a walk of about seventeen seconds.
227
522
  *
228
523
  * The trail cannot follow it - the south end is pinned to the Rat Tunnel
229
- * far-side exit and the middle is a causeway across a fixed lake - so the last
230
- * stretch of road turns east off the trail head instead. See TOWN_APPROACH_Z.
524
+ * far-side exit and its north end is pinned to the lake's south shore - so the
525
+ * road turns east off the trail head along that shore and crosses on the gate
526
+ * axis instead. See CALVERTON_BRIDGE_X and CALVERTON_SHORE_ROAD_Z.
231
527
  *
232
528
  * Raised from 40 to 45 when the walls widened. The west wall is pinned by the
233
529
  * trail's tree screen, which reaches to x = AMAZON_TRAIL_X + 12: pushing the
@@ -247,9 +543,28 @@ export const TOWN_CENTER_X = AMAZON_TRAIL_X + TOWN_EAST_OFFSET;
247
543
  * of masonry with slots cut in it rather than houses with yards.
248
544
  */
249
545
  export const TOWN_MIN_X = TOWN_CENTER_X - 37;
250
- export const TOWN_MAX_X = TOWN_CENTER_X + 37;
251
- export const TOWN_SOUTH_Z = AMAZON_TRAIL_NORTH_WORLD_Z - 2;
252
- export const TOWN_NORTH_Z = TOWN_SOUTH_Z - 62;
546
+ /**
547
+ * Grown again on 2026-09-16, 75x63 -> 95x75, east and north only: the west
548
+ * wall is pinned by the trail's tree screen and the south by the trail head.
549
+ * At 75x63 the closest buildings still stood four tiles apart and a shop
550
+ * door opened onto the market stalls; the extra ground goes between them.
551
+ * The main street stays on the trail-head axis, so the town is deeper on
552
+ * its east side.
553
+ */
554
+ export const TOWN_MAX_X = TOWN_CENTER_X + 57;
555
+ /**
556
+ * The south wall used to stand two rows off the lake shore, which put the
557
+ * gatehouse beside the water: you stepped off the bridge and were in the
558
+ * town. Pulled north (2 -> 22) so the approach road has a proper leg up from
559
+ * the shore, and the town reads as standing back from the water rather than
560
+ * on it. The fishery keeps its ground (FISHERY_TOWN_GAP).
561
+ *
562
+ * Measured from the bank row the bridge lands on (TOWN_APPROACH_Z), not from
563
+ * the trail: the trail's north end moved to the south shore when the bridge
564
+ * moved onto the gate axis, and the town must not move with it.
565
+ */
566
+ export const TOWN_SOUTH_Z = BATTLEFIELD_LAKE_MIN_Z - 2 - 22;
567
+ export const TOWN_NORTH_Z = TOWN_SOUTH_Z - 74;
253
568
  /** Walkable interior, one tile inside the wall on every side. */
254
569
  export const TOWN_INNER_MIN_X = TOWN_MIN_X + 1;
255
570
  export const TOWN_INNER_MAX_X = TOWN_MAX_X - 1;
@@ -268,30 +583,55 @@ export const TOWN_GATE_X = TOWN_CENTER_X;
268
583
  */
269
584
  export const TOWN_GATE_HALF_W = 2;
270
585
  /**
271
- * The elbow that carries the trail head east to the south gate.
272
- *
273
- * Sits one row south of the trail's last row, so the two overlap and the join
274
- * reads as one road bending rather than two roads meeting. It runs east at this
275
- * Z to TOWN_GATE_X, then north into the gate throat.
586
+ * The bank row the Calverton bridge lands on: the gate road's last row at
587
+ * town height, one row north of the shore row that carries the ramp down to
588
+ * the deck. The gate road runs from here north into the gate throat.
276
589
  */
277
- export const TOWN_APPROACH_Z = AMAZON_TRAIL_NORTH_WORLD_Z + 1;
278
- export const TOWN_APPROACH_HALF_W = 1;
590
+ export const TOWN_APPROACH_Z = BATTLEFIELD_LAKE_MIN_Z - 2;
591
+ /**
592
+ * The Calverton bridge and the shore road that feeds it.
593
+ *
594
+ * The bridge crosses the lake on the gate axis, five planks wide like the
595
+ * gate road, so a player walks off the deck straight up to the gate. The
596
+ * shore road runs east along the lake's south bank from the trail head to
597
+ * the bridge foot, three tiles wide, its north row being the trail's last
598
+ * row. Both sit inside the sealed trail corridor: the ground south of the
599
+ * lake is walkable from the South area, so the road's south verge and its
600
+ * east end carry the same tree screen as the trail (see the map's trail
601
+ * tree block) or the Rat Tunnel stops being the only way into the East.
602
+ */
603
+ export const CALVERTON_BRIDGE_X = TOWN_GATE_X;
604
+ export const CALVERTON_BRIDGE_HALF_W = TOWN_GATE_HALF_W;
605
+ export const CALVERTON_SHORE_ROAD_Z = BATTLEFIELD_LAKE_MAX_Z + 3;
606
+ export const CALVERTON_SHORE_ROAD_HALF_W = 1;
607
+ /** The deck plus both ramp rows: everything a player stands on between the two banks. */
608
+ export function inCalvertonBridge(x, z) {
609
+ return (Math.abs(x - CALVERTON_BRIDGE_X) <= CALVERTON_BRIDGE_HALF_W &&
610
+ z >= BATTLEFIELD_LAKE_MIN_Z - 1 &&
611
+ z <= BATTLEFIELD_LAKE_MAX_Z + 1);
612
+ }
613
+ /** The three rows of the shore road, from the trail lane's west edge to the bridge's east edge. */
614
+ export function inCalvertonShoreRoad(x, z) {
615
+ return (x >= AMAZON_TRAIL_X - AMAZON_TRAIL_HALF_W &&
616
+ x <= CALVERTON_BRIDGE_X + CALVERTON_BRIDGE_HALF_W &&
617
+ Math.abs(z - CALVERTON_SHORE_ROAD_Z) <= CALVERTON_SHORE_ROAD_HALF_W);
618
+ }
279
619
  /**
280
620
  * Main street runs north-south on the town axis; the cross street runs
281
621
  * east-west.
282
622
  *
283
- * The offset from the north wall went 26 -> 40 with the walls, which keeps the
284
- * cross street on the same world row it has always been on. That matters: the
623
+ * The offset from the north wall went 26 -> 40 -> 52 with the walls, which
624
+ * keeps the cross street on the same world row it has always been on. That matters: the
285
625
  * Dreadwoods east gate, its road, and the spider cave track are all cut on this
286
626
  * line, and none of them wanted to move because the town grew a wider north
287
627
  * quarter.
288
628
  */
289
629
  export const TOWN_MAIN_STREET_HALF_W = 2;
290
- export const TOWN_CROSS_STREET_Z = TOWN_NORTH_Z + 40;
630
+ export const TOWN_CROSS_STREET_Z = TOWN_NORTH_Z + 52;
291
631
  export const TOWN_CROSS_STREET_HALF_W = 1;
292
632
  /** Market square, centred where the two streets meet. */
293
- export const TOWN_SQUARE_HALF_W = 6;
294
- export const TOWN_SQUARE_HALF_D = 4;
633
+ export const TOWN_SQUARE_HALF_W = 9;
634
+ export const TOWN_SQUARE_HALF_D = 6;
295
635
  /* ------------------------------------------------------------------ *
296
636
  * The Dreadwoods east gate - the way into the woods from Calverton.
297
637
  *
@@ -367,8 +707,21 @@ export const FISHERY_MAX_X = TOWN_CENTER_X + 30;
367
707
  * country between them, walked on its own road (see the fishery road in
368
708
  * map.ts). There is nothing but empty grass up here: the East runs north to the
369
709
  * sea cliff, and the fishery is still a long way short of it.
710
+ *
711
+ * Reduced 70 -> 50 when the town moved twenty rows north off the lake shore,
712
+ * and 50 -> 38 when the north wall grew twelve rows, on the mistaken note
713
+ * that the fishery's north edge was up against the northern wall's approach.
714
+ * That wall is x 4-239; east of the Dreadwoods there is nothing north of
715
+ * here but open grass for 460 rows to the sea-cliff rim. At 38 the compound
716
+ * started five rows past the town's apron and read as its back yard again.
717
+ *
718
+ * 190 now (James, 2026-09-16: "at least 4-5x further, we need to create
719
+ * space so the game feels natural"). The north fence lands at the foot of
720
+ * the ridge on rows 303-312, so the falls pour off high ground, and the walk
721
+ * from the town gate is about 47 seconds of country road. Anything up to
722
+ * about 490 still fits before the skirt reaches the rim.
370
723
  */
371
- export const FISHERY_TOWN_GAP = 70;
724
+ export const FISHERY_TOWN_GAP = 190;
372
725
  export const FISHERY_SOUTH_Z = TOWN_NORTH_Z - FISHERY_TOWN_GAP;
373
726
  export const FISHERY_NORTH_Z = FISHERY_SOUTH_Z - 46;
374
727
  export const FISHERY_INNER_MIN_X = FISHERY_MIN_X + 1;
@@ -472,9 +825,16 @@ export const BOB_SPAWN_Z = TOWN_CROSS_STREET_Z - 6;
472
825
  export const ANNES_SPUR_TILES = 12;
473
826
  export const ANNES_X = Math.round((CROSSROADS_WORLD_X + BASECAMP_CENTER_X) / 2);
474
827
  export const ANNES_HOUSE_LOCAL_Z = Math.round((CROSSROADS_LOCAL_Z + BASECAMP_CENTER_LOCAL_Z) / 2) - ANNES_SPUR_TILES;
475
- export const ANNES_ZONE_RX = 12;
828
+ export const ANNES_ZONE_RX = 32;
476
829
  export const ANNES_ZONE_NORTH = 16;
477
- export const ANNES_ZONE_SOUTH = 22;
830
+ export const ANNES_ZONE_SOUTH = 40;
831
+ /** Neighbours around Anne's green. Z stays local until world expansion. */
832
+ export const ANNES_NEIGHBOUR_HOMES = [
833
+ { name: 'Eldric', x: ANNES_X + 20, z: ANNES_HOUSE_LOCAL_Z, style: 'cottage-slate' },
834
+ { name: 'Rowan', x: ANNES_X - 17, z: ANNES_HOUSE_LOCAL_Z + 26, style: 'cottage-moss' },
835
+ { name: 'Mara', x: ANNES_X + 18, z: ANNES_HOUSE_LOCAL_Z + 26, style: 'cottage-russet' },
836
+ ];
837
+ export const ANNES_GREEN = { x: ANNES_X + 1, z: ANNES_HOUSE_LOCAL_Z + 20 };
478
838
  /**
479
839
  * Small vestigial mud alcove right behind the Suspicious hole mouth at Base
480
840
  * Camp - kept only as set dressing for the doorway. The real Rat Tunnel
@@ -679,8 +1039,9 @@ export const FOREST_DEN_SITES = [
679
1039
  forestDenSite('pond-wilds', 360, 45, 'north', 5),
680
1040
  forestDenSite('pond-east', 384, 42, 'south', 6),
681
1041
  forestDenSite('trail-oak', 428, 42, 'east', 7),
682
- forestDenSite('camp-approach', 472, 42, 'south', 8),
683
- forestDenSite('camp-wilds', 500, 8, 'south', 9),
1042
+ // Keep rotating cave mouths in the woods beyond the residential plots.
1043
+ forestDenSite('camp-approach', ANNES_X + ANNES_ZONE_RX + 24, 45, 'south', 8),
1044
+ forestDenSite('camp-wilds', ANNES_X + ANNES_ZONE_RX + 30, 8, 'south', 9),
684
1045
  forestDenSite('wall-woods', 556, 42, 'west', 10),
685
1046
  forestDenSite('south-belt', 170, 50, 'north', 11),
686
1047
  forestDenSite('mid-woods', 310, 8, 'east', 12),
@@ -885,26 +1246,8 @@ export const DUNGEON_ROOM_SPAWNS = {
885
1246
  * East edge meets the Deadlands west border. The lake continues east of this pad
886
1247
  * (see BATTLEFIELD_LAKE_EAST_X).
887
1248
  */
888
- /**
889
- * How far north the whole front line sits from the farm.
890
- *
891
- * The south palisade used to stand one tile off No Harm Farm's own north
892
- * fence, so a player walked out of the farm gate straight into a war. Every
893
- * absolute Z on the pad is written as its original survey number put through
894
- * `bfZ`, which keeps the internal layout - lake, camps, cabbage rows, the
895
- * bridge span, the Captain's parade ground - exactly as it was designed and
896
- * moves the whole thing as one piece.
897
- *
898
- * The ceiling on this number is the lake, not the farm. The water sheet runs
899
- * on east through the corridor south of the Dreadwoods, whose own south edge
900
- * is DEADLANDS_MAX_Z (639); the wall returns need a band between the two. At
901
- * 18 the lake's north bank lands at 648, leaving that corridor 9 tiles deep
902
- * with the returns still reaching it. Push much past this and the lake starts
903
- * eating the Dreadwoods' south wall.
904
- */
905
- export const BATTLEFIELD_NORTH_SHIFT = 18;
906
- /** An original survey Z, moved north by BATTLEFIELD_NORTH_SHIFT. */
907
- const bfZ = (surveyZ) => surveyZ - BATTLEFIELD_NORTH_SHIFT;
1249
+ // BATTLEFIELD_NORTH_SHIFT, bfZ and the two lake rows are declared up by the
1250
+ // Amazon Trail: the trail and Calverton are measured from the lake.
908
1251
  export const BATTLEFIELD_MIN_X = 10;
909
1252
  export const BATTLEFIELD_MAX_X = DEADLANDS_MIN_X - 1;
910
1253
  export const BATTLEFIELD_MIN_WORLD_Z = bfZ(648);
@@ -912,15 +1255,6 @@ export const BATTLEFIELD_MIN_WORLD_Z = bfZ(648);
912
1255
  export const BATTLEFIELD_MAX_WORLD_Z = bfZ(748);
913
1256
  export const BATTLEFIELD_MIN_LOCAL_Z = BATTLEFIELD_MIN_WORLD_Z - WORLD_NORTH_PAD;
914
1257
  export const BATTLEFIELD_MAX_LOCAL_Z = BATTLEFIELD_MAX_WORLD_Z - WORLD_NORTH_PAD;
915
- /**
916
- * Lake between soldier (south / farmward) and goblin (north) camps.
917
- * Water blocks walking; the battlefield crossing is the long bridge once repaired.
918
- * Thick N-S band on the pad, then the same Z band continues east through the
919
- * corridor south of the Deadlands, past the west-face enclave, until it meets
920
- * the SE high-rock row.
921
- */
922
- export const BATTLEFIELD_LAKE_MIN_Z = bfZ(666);
923
- export const BATTLEFIELD_LAKE_MAX_Z = bfZ(698);
924
1258
  /** Solid southward wall returns join both Dreadwoods corners to the lake bank. */
925
1259
  export const DEADLANDS_SOUTH_WALL_XS = [DEADLANDS_MIN_X, DEADLANDS_MAX_X];
926
1260
  export const DEADLANDS_SOUTH_WALL_END_Z = BATTLEFIELD_LAKE_MIN_Z - 1;
@@ -1021,35 +1355,45 @@ export function inNorthernGarrison(x, z) {
1021
1355
  * jetty iced in at the east bank and a small shelter where the snowfields
1022
1356
  * approach meets the shore. Nothing is fished here yet; it is the place first.
1023
1357
  * World coordinates - z already includes WORLD_NORTH_PAD.
1358
+ *
1359
+ * The lake sat at x=32 until 2026-09-17, its west bank six tiles from the
1360
+ * landing's works; it moved twenty-four tiles east so Branson's bay could
1361
+ * open up. Everything on it is placed from the centre; the fixed points
1362
+ * below moved with it.
1024
1363
  */
1025
- export const FROZEN_LAKE_CENTER = { x: 32, z: 236 };
1364
+ export const FROZEN_LAKE_CENTER = { x: 56, z: 236 };
1026
1365
  export const FROZEN_LAKE_RX = 14;
1027
1366
  export const FROZEN_LAKE_RZ = 12;
1028
1367
  /** The level the ice is frozen at; the lowland around it already lies near it. */
1029
1368
  export const FROZEN_LAKE_ICE_HEIGHT = 0;
1030
- /** Holes cut through the ice. The first two have a fisher sitting beside them. */
1369
+ /**
1370
+ * The fishers' holes, one each, cut for good. Every other hole on the lake is
1371
+ * the server's rota of thin-ice spots (see ICE_SPOT_MAX in woodwork.ts), so
1372
+ * nothing else is placed by the generator.
1373
+ */
1031
1374
  export const FROZEN_LAKE_HOLES = [
1032
- { x: 26, z: 232 },
1033
- { x: 36, z: 240 },
1034
- { x: 22, z: 238 },
1035
- { x: 30, z: 244 },
1036
- { x: 40, z: 232 },
1037
- { x: 34, z: 229 },
1375
+ { x: FROZEN_LAKE_CENTER.x - 6, z: FROZEN_LAKE_CENTER.z - 4 },
1376
+ { x: FROZEN_LAKE_CENTER.x + 4, z: FROZEN_LAKE_CENTER.z + 4 },
1038
1377
  ];
1039
1378
  /** Where each fisher sits, and the hole they face. */
1040
1379
  export const FROZEN_LAKE_FISHERS = [
1041
- { name: 'Halvard', x: 27, z: 232, faceX: 26, faceZ: 232 },
1042
- { name: 'Ingrid', x: 36, z: 241, faceX: 36, faceZ: 240 },
1380
+ { name: 'Halvard', x: FROZEN_LAKE_HOLES[0].x + 1, z: FROZEN_LAKE_HOLES[0].z, faceX: FROZEN_LAKE_HOLES[0].x, faceZ: FROZEN_LAKE_HOLES[0].z },
1381
+ { name: 'Ingrid', x: FROZEN_LAKE_HOLES[1].x, z: FROZEN_LAKE_HOLES[1].z + 1, faceX: FROZEN_LAKE_HOLES[1].x, faceZ: FROZEN_LAKE_HOLES[1].z },
1043
1382
  ];
1044
1383
  /** Where the jetty leaves the east bank, and how many tiles it reaches west. */
1045
- export const FROZEN_LAKE_JETTY = { x: 45, z: 236, length: 4 };
1384
+ export const FROZEN_LAKE_JETTY = { x: FROZEN_LAKE_CENTER.x + 13, z: FROZEN_LAKE_CENTER.z, length: 4 };
1046
1385
  /**
1047
1386
  * The bait table on the east bank, between the supply sled and the cooking
1048
1387
  * fire, and the bait master who works it: he stands at its east end, facing
1049
1388
  * the table and the ice beyond it.
1050
1389
  */
1051
- export const FROZEN_LAKE_BAIT_TABLE = { x: 46, z: 243 };
1052
- export const FROZEN_LAKE_BAIT_MASTER = { x: 47, z: 243, faceX: 46, faceZ: 243 };
1390
+ export const FROZEN_LAKE_BAIT_TABLE = { x: FROZEN_LAKE_CENTER.x + 14, z: FROZEN_LAKE_CENTER.z + 7 };
1391
+ export const FROZEN_LAKE_BAIT_MASTER = {
1392
+ x: FROZEN_LAKE_BAIT_TABLE.x + 1,
1393
+ z: FROZEN_LAKE_BAIT_TABLE.z,
1394
+ faceX: FROZEN_LAKE_BAIT_TABLE.x,
1395
+ faceZ: FROZEN_LAKE_BAIT_TABLE.z,
1396
+ };
1053
1397
  /** Signed distance to the ice edge in tiles: at or below zero is on the ice. */
1054
1398
  export function frozenLakeEdgeDistance(x, z) {
1055
1399
  const d = Math.hypot((x - FROZEN_LAKE_CENTER.x) / FROZEN_LAKE_RX, (z - FROZEN_LAKE_CENTER.z) / FROZEN_LAKE_RZ);
@@ -1102,6 +1446,20 @@ export const ICE_PRISON_ENTRY = { x: ICE_PRISON_CX, z: ICE_PRISON_CELLS.maxZ - 2
1102
1446
  /** Gate styles carry the facing letter first; this tag picks the destination. */
1103
1447
  export const ICE_PRISON_GATE_IN_STYLE = 'n-iceprison';
1104
1448
  export const ICE_PRISON_GATE_OUT_STYLE = 's-iceprison-out';
1449
+ /**
1450
+ * The rune arch at the hallway's mouth is sealed, and only a Frozen Key opens
1451
+ * it (James, 2026-09-18: the Frozen Hallway needs the Frozen Key to enter).
1452
+ * The style keeps 'sealed' so the asset draws the slab and the footprint
1453
+ * blocks all five tiles; the server passes a keyholder through by teleport,
1454
+ * the way the dungeon gates work, and lets anyone back out.
1455
+ */
1456
+ export const FROZEN_HALLWAY_SEAL_STYLE = 'n-sealed-hallway';
1457
+ export function isFrozenHallwaySealStyle(style) {
1458
+ return style === FROZEN_HALLWAY_SEAL_STYLE;
1459
+ }
1460
+ /** Where the arch puts you: inside the corridor, or back out on the approach. */
1461
+ export const FROZEN_HALLWAY_SEAL_INSIDE_DZ = -3;
1462
+ export const FROZEN_HALLWAY_SEAL_OUTSIDE_DZ = 3;
1105
1463
  /** Where the way out drops you: just south of the hallway's gate. */
1106
1464
  export const ICE_PRISON_OUTSIDE = { x: FROZEN_HALLWAY_X, z: FROZEN_HALLWAY_GATE_Z + 3 };
1107
1465
  /** The seal on the shrine's platform; rings step up around it. */
@@ -1152,6 +1510,222 @@ export function inFrozenHallwayCorridor(x, z) {
1152
1510
  return (z >= FROZEN_HALLWAY_GATE_Z && z <= FROZEN_HALLWAY_MOUTH_Z &&
1153
1511
  Math.abs(x - frozenHallwayCenterAt(z)) <= frozenHallwayHalfWidthAt(z));
1154
1512
  }
1513
+ /**
1514
+ * The ice cavern roster, placed along the hallway in the order a player
1515
+ * meets it (2026-09-20): skitters in the first chamber inside the sealed
1516
+ * arch, revenants either end of the throat, lurkers in the second chamber,
1517
+ * and the troll on the last stretch before the iron gate. Tiles are on the
1518
+ * corridor's centre line offset by `dx`, so they follow the S as the swing
1519
+ * constants change; the builder's props (the fallen pillars, traps and
1520
+ * spire fields sit at other offsets) are skipped by canStand at spawn.
1521
+ * After the constants that frozenHallwayCenterAt reads, or the module
1522
+ * hangs in the TDZ.
1523
+ */
1524
+ export const FROZEN_HALLWAY_SPAWNS = [
1525
+ { kind: 'crystalskitter', x: frozenHallwayCenterAt(97) + 3, z: 97 },
1526
+ { kind: 'crystalskitter', x: frozenHallwayCenterAt(89) - 3, z: 89 },
1527
+ { kind: 'crystalskitter', x: frozenHallwayCenterAt(83) + 1, z: 83 },
1528
+ { kind: 'frostrevenant', x: frozenHallwayCenterAt(71) - 1, z: 71 },
1529
+ { kind: 'frostrevenant', x: frozenHallwayCenterAt(58) + 1, z: 58 },
1530
+ { kind: 'glaciallurker', x: frozenHallwayCenterAt(50) - 2, z: 50 },
1531
+ { kind: 'glaciallurker', x: frozenHallwayCenterAt(43) + 3, z: 43 },
1532
+ { kind: 'icetroll', x: frozenHallwayCenterAt(31), z: 31 },
1533
+ ];
1534
+ /**
1535
+ * The thaw crossing: Frozen east rim into the untouched north-eastern
1536
+ * kingdom along DEADLANDS_MIN_X. The north sea comes in through the sea
1537
+ * cliff as a rock-walled inlet AT SEA LEVEL; one waterfall drops it into a
1538
+ * gorge cut below the land, and the low river runs south under the spans to
1539
+ * the battlefield lake. Three east spans are personal woodwork repairs.
1540
+ * World coordinates. Rains' first crossing is (230, 49).
1541
+ *
1542
+ * Heights: the sea and the inlet are 0, the river floor is THAW_GORGE_FLOOR_H
1543
+ * at the fall's foot, and the rims are the natural land (THAW_RIM_H easing to
1544
+ * 3 at the Dreadwoods). Nothing here is built up above the ground: the
1545
+ * 2026-09-17 pass had the inlet at 8 and the rims at 10 on a snowfield of 3-4,
1546
+ * so the fall hung in the sky above the water feeding it and the bank paths
1547
+ * were a levee nobody could climb onto.
1548
+ */
1549
+ export const THAW_GORGE_CX = DEADLANDS_MIN_X;
1550
+ export const THAW_GORGE_MIN_X = THAW_GORGE_CX - 4;
1551
+ export const THAW_GORGE_MAX_X = THAW_GORGE_CX + 4;
1552
+ export const THAW_GORGE_MIN_Z = 0;
1553
+ /** Low river meets the battlefield lake's north bank. */
1554
+ export const THAW_GORGE_MAX_Z = BATTLEFIELD_LAKE_MIN_Z;
1555
+ export const THAW_BRIDGE_Z = 49;
1556
+ export const THAW_MID_BRIDGE_Z = 110;
1557
+ export const THAW_WOODS_BRIDGE_Z = DEADLANDS_MIN_Z - 14;
1558
+ export const THAW_BRIDGE_ZS = [THAW_BRIDGE_Z, THAW_MID_BRIDGE_Z, THAW_WOODS_BRIDGE_Z];
1559
+ export const THAW_BRIDGE_HALF_W = 1;
1560
+ /**
1561
+ * The river floor at the foot of the fall: eight below the sea, the height
1562
+ * the waterfall asset's sheet drops (FISHERY_FALL_HEIGHT). The only negative
1563
+ * terrain in the world; the renderer and the walk rules only ever compare
1564
+ * heights, so a cut below zero is just a deeper cut.
1565
+ */
1566
+ export const THAW_GORGE_FLOOR_H = -8;
1567
+ /** The inlet is the sea: same height as the north sea it comes in from. */
1568
+ export const THAW_FLOOD_H = 0;
1569
+ /** Rock rim and bank beside the inlet, above the water like any shore. */
1570
+ export const THAW_RIM_H = 4;
1571
+ /** Tiles beyond the rim over which the bank grades back to the natural land. */
1572
+ export const THAW_BANK_APRON = 12;
1573
+ export const THAW_FLOOD_WEST_X = THAW_GORGE_CX - 16;
1574
+ export const THAW_FLOOD_EAST_X = THAW_GORGE_CX + 18;
1575
+ /**
1576
+ * The one drop, north of the first east span. Sea to Dreadwoods: flood,
1577
+ * then this fall, then the bridges over the low water. Not at Rains' span
1578
+ * and not south of it.
1579
+ */
1580
+ export const THAW_FALLS_Z = 26;
1581
+ /** @deprecated Same tile as THAW_FALLS_Z; kept for older imports. */
1582
+ export const THAW_SIDE_FALLS_Z = THAW_FALLS_Z;
1583
+ export const THAW_ARCH_X = THAW_FLOOD_EAST_X + 7;
1584
+ export const THAW_ARCH_PILLAR_DZ = 2;
1585
+ export const THAW_MOUTH_STAND_X = THAW_FLOOD_WEST_X - 2;
1586
+ export const THAW_MOUTH_STAND_Z = 6;
1587
+ export const THAW_FALLS_STAND_X = THAW_FLOOD_WEST_X - 2;
1588
+ export const THAW_FALLS_STAND_Z = THAW_FALLS_Z;
1589
+ export function thawEastBridgeIdAtZ(z) {
1590
+ for (let i = 0; i < THAW_BRIDGE_ZS.length; i++) {
1591
+ if (Math.abs(z - THAW_BRIDGE_ZS[i]) <= THAW_BRIDGE_HALF_W) {
1592
+ return (i + 1);
1593
+ }
1594
+ }
1595
+ return null;
1596
+ }
1597
+ export function inThawFlood(x, z) {
1598
+ return (z >= THAW_GORGE_MIN_Z &&
1599
+ z <= THAW_FALLS_Z &&
1600
+ x >= THAW_FLOOD_WEST_X &&
1601
+ x <= THAW_FLOOD_EAST_X);
1602
+ }
1603
+ export function thawGorgeWestX(z) {
1604
+ if (z <= THAW_FALLS_Z)
1605
+ return THAW_FLOOD_WEST_X;
1606
+ return THAW_GORGE_MIN_X;
1607
+ }
1608
+ export function thawGorgeEastX(z) {
1609
+ if (z <= THAW_FALLS_Z)
1610
+ return THAW_FLOOD_EAST_X;
1611
+ if (z >= DEADLANDS_MIN_Z)
1612
+ return DEADLANDS_MIN_X - 1;
1613
+ return THAW_GORGE_MAX_X;
1614
+ }
1615
+ /** Walkable timber through the west gate, over the low lake-feed. */
1616
+ export function onThawWestGateDeck(x, z) {
1617
+ return (x >= DEADLANDS_MIN_X - 4 &&
1618
+ x <= DEADLANDS_MIN_X - 1 &&
1619
+ Math.abs(z - DREADWOODS_WEST_GATE_Z) <= DREADWOODS_WEST_GATE_HALF_W);
1620
+ }
1621
+ export function onThawBridgeDeck(x, z) {
1622
+ return onThawWestGateDeck(x, z);
1623
+ }
1624
+ export function inThawGorgeCut(x, z) {
1625
+ return (z >= THAW_GORGE_MIN_Z &&
1626
+ z <= THAW_GORGE_MAX_Z &&
1627
+ x >= thawGorgeWestX(z) &&
1628
+ x <= thawGorgeEastX(z));
1629
+ }
1630
+ export function inThawEastBridgeGap(x, z) {
1631
+ if (thawEastBridgeIdAtZ(z) === null)
1632
+ return false;
1633
+ return inThawGorgeCut(x, z) && !onThawWestGateDeck(x, z);
1634
+ }
1635
+ export function thawEastBridgeAt(x, z) {
1636
+ if (!inThawEastBridgeGap(x, z))
1637
+ return null;
1638
+ return thawEastBridgeIdAtZ(z);
1639
+ }
1640
+ export function isThawEastBridgeStyle(style) {
1641
+ return (style ?? '').includes('-thaw');
1642
+ }
1643
+ export function thawEastBridgeIdFromStyle(style) {
1644
+ const match = (style ?? '').match(/thaw([123])/);
1645
+ if (!match)
1646
+ return null;
1647
+ return Number(match[1]);
1648
+ }
1649
+ export function thawEastBridgeStyle(id, kind, edge) {
1650
+ const base = `${kind}-thaw${id}-ew`;
1651
+ return edge ? `${base}-${edge}` : base;
1652
+ }
1653
+ export function personalThawEastCrossingStyle(brokenStyle) {
1654
+ const style = brokenStyle ?? '';
1655
+ if (!style.startsWith('broken') || !style.includes('-thaw'))
1656
+ return style;
1657
+ return `crossing${style.slice('broken'.length)}`;
1658
+ }
1659
+ export function thawEastCrossingExitX(id, fromX) {
1660
+ const z = THAW_BRIDGE_ZS[id - 1];
1661
+ const west = thawGorgeWestX(z) - 2;
1662
+ const east = thawGorgeEastX(z) + 2;
1663
+ return fromX < (west + east) / 2 ? east : west;
1664
+ }
1665
+ /**
1666
+ * Rim height beside the cut at row z: the inlet's rock rim north of the
1667
+ * fall, easing to the wall's height at the Dreadwoods. The bank paths, the
1668
+ * personal spans and the wreckage lift all read it, so a deck is level with
1669
+ * the ground either side of it. It tracks the natural land (1-6 here), and
1670
+ * the builder grades the bank out to it over THAW_BANK_APRON tiles.
1671
+ */
1672
+ export function thawRimHeightAt(z) {
1673
+ if (z <= THAW_FALLS_Z)
1674
+ return THAW_RIM_H;
1675
+ if (z < DEADLANDS_MIN_Z) {
1676
+ const t = (z - THAW_FALLS_Z) / Math.max(1, DEADLANDS_MIN_Z - THAW_FALLS_Z);
1677
+ return Math.round(THAW_RIM_H * (1 - t) + 3 * t);
1678
+ }
1679
+ return 3;
1680
+ }
1681
+ /**
1682
+ * Water height in the cut at row z: the inlet at sea level down to the
1683
+ * fall, then THAW_GORGE_FLOOR_H at the fall's foot rising by one step every
1684
+ * eighty-odd rows to meet the lake at 0. A step that size between two water
1685
+ * tiles averages into a slope the eye cannot find; the river reads as one
1686
+ * level run under the spans.
1687
+ */
1688
+ export function thawRiverHeightAt(z) {
1689
+ if (z <= THAW_FALLS_Z)
1690
+ return THAW_FLOOD_H;
1691
+ const t = (z - THAW_FALLS_Z - 1) / Math.max(1, THAW_GORGE_MAX_Z - THAW_FALLS_Z - 1);
1692
+ // `|| 0` turns the -0 that rounding a negative floor to nothing yields
1693
+ // into a plain 0, so a strict comparison with the lake's height holds.
1694
+ return Math.round(THAW_GORGE_FLOOR_H * (1 - Math.min(1, t))) || 0;
1695
+ }
1696
+ /**
1697
+ * The repaired span is a deck at rim height, high over the low water (the
1698
+ * 2026-09-17 first pass laid it at 1, on the river, so the crossing was a
1699
+ * plank raft at the bottom of an eight-deep cut).
1700
+ */
1701
+ export function thawEastPersonalDeckH(id) {
1702
+ return thawRimHeightAt(THAW_BRIDGE_ZS[id - 1]);
1703
+ }
1704
+ /** How far a span's deck hangs above the water it crosses, in height units. */
1705
+ export function thawEastSpanClearance(id) {
1706
+ return thawEastPersonalDeckH(id) - thawRiverHeightAt(THAW_BRIDGE_ZS[id - 1]);
1707
+ }
1708
+ /** The fall pours over the whole width of the cut below the basin. */
1709
+ export const THAW_FALLS_HALF_W = THAW_GORGE_MAX_X - THAW_GORGE_CX;
1710
+ export function inThawGorgeStone(x, z) {
1711
+ if (inThawFlood(x, z)) {
1712
+ return (x === THAW_FLOOD_WEST_X - 1 ||
1713
+ x === THAW_FLOOD_EAST_X + 1 ||
1714
+ z === THAW_GORGE_MIN_Z + 2 ||
1715
+ z === THAW_FALLS_Z);
1716
+ }
1717
+ return (z > THAW_FALLS_Z &&
1718
+ z <= THAW_GORGE_MAX_Z + 2 &&
1719
+ x >= thawGorgeWestX(z) - 2 &&
1720
+ x <= thawGorgeEastX(z) + 2);
1721
+ }
1722
+ export function inThawSnowApproach(x, z) {
1723
+ return (x < thawGorgeWestX(z) &&
1724
+ x >= thawGorgeWestX(z) - (z < DEADLANDS_MIN_Z ? THAW_BANK_APRON + 6 : 8) &&
1725
+ z >= THAW_GORGE_MIN_Z &&
1726
+ z <= THAW_GORGE_MAX_Z &&
1727
+ !inThawGorgeCut(x, z));
1728
+ }
1155
1729
  /** Half-width of the walkable bridge deck (tiles either side of bridge X). */
1156
1730
  export const BATTLEFIELD_BRIDGE_HALF_W = 1;
1157
1731
  /**
@@ -1270,6 +1844,46 @@ export const BATTLEFIELD_GOBLIN_CAMPS = [
1270
1844
  { x: 72, z: bfZ(650) },
1271
1845
  { x: 128, z: bfZ(656) },
1272
1846
  ];
1847
+ /** The goblins' ruined hinterland between their battle line and the first snow. */
1848
+ export const GOBLIN_FRONTIER_MIN_X = 28;
1849
+ export const GOBLIN_FRONTIER_MAX_X = 157;
1850
+ export const GOBLIN_FRONTIER_MIN_Z = SNOW_EDGE_Z;
1851
+ export const GOBLIN_FRONTIER_MAX_Z = BATTLEFIELD_MIN_WORLD_Z - 4;
1852
+ export const GOBLIN_FRONTIER_TOWERS = [
1853
+ { x: BATTLEFIELD_BRIDGE_X - 27, z: GOBLIN_FRONTIER_MAX_Z - 21 },
1854
+ { x: BATTLEFIELD_BRIDGE_X + 27, z: GOBLIN_FRONTIER_MIN_Z + 21 },
1855
+ ];
1856
+ /** Five-wide approach; the bends leave the existing camp huts and spawns alone. */
1857
+ export const GOBLIN_FRONTIER_ROAD = [
1858
+ { x: BATTLEFIELD_BRIDGE_X, z: BATTLEFIELD_LAKE_MIN_Z - 3 },
1859
+ { x: BATTLEFIELD_BRIDGE_X + 1, z: BATTLEFIELD_MIN_WORLD_Z + 6 },
1860
+ { x: BATTLEFIELD_BRIDGE_X - 6, z: GOBLIN_FRONTIER_MAX_Z - 4 },
1861
+ { x: BATTLEFIELD_BRIDGE_X - 10, z: GOBLIN_FRONTIER_MAX_Z - 17 },
1862
+ { x: BATTLEFIELD_BRIDGE_X, z: GOBLIN_FRONTIER_MAX_Z - 31 },
1863
+ { x: BATTLEFIELD_BRIDGE_X + 9, z: GOBLIN_FRONTIER_MIN_Z + 37 },
1864
+ { x: BATTLEFIELD_BRIDGE_X + 1, z: GOBLIN_FRONTIER_MIN_Z + 18 },
1865
+ { x: BATTLEFIELD_BRIDGE_X, z: GOBLIN_FRONTIER_MIN_Z - 7 },
1866
+ ];
1867
+ /** The two occupied camp loops off the frontier road (built in goblinFrontier.ts). */
1868
+ export const GOBLIN_FRONTIER_CAMPS = [
1869
+ { x: 113, z: 613 },
1870
+ { x: 68, z: 571 },
1871
+ ];
1872
+ /**
1873
+ * Where the frontier is held. Goblins spawn here the way they do at the
1874
+ * battle-line camps, and the goblin camp chest rotates through these sites
1875
+ * as well as those. A tower or a camp keeps a full band; a road gate has a
1876
+ * picket, so the road north is watched without becoming a wall.
1877
+ */
1878
+ export const GOBLIN_FRONTIER_SPAWNS = [
1879
+ { x: GOBLIN_FRONTIER_TOWERS[0].x, z: GOBLIN_FRONTIER_TOWERS[0].z + 4, count: 6 },
1880
+ { x: GOBLIN_FRONTIER_TOWERS[1].x, z: GOBLIN_FRONTIER_TOWERS[1].z + 4, count: 6 },
1881
+ { x: GOBLIN_FRONTIER_CAMPS[0].x, z: GOBLIN_FRONTIER_CAMPS[0].z, count: 6 },
1882
+ { x: GOBLIN_FRONTIER_CAMPS[1].x, z: GOBLIN_FRONTIER_CAMPS[1].z, count: 6 },
1883
+ { x: GOBLIN_FRONTIER_ROAD[2].x, z: GOBLIN_FRONTIER_ROAD[2].z, count: 3 },
1884
+ { x: GOBLIN_FRONTIER_ROAD[4].x, z: GOBLIN_FRONTIER_ROAD[4].z, count: 3 },
1885
+ { x: GOBLIN_FRONTIER_ROAD[6].x, z: GOBLIN_FRONTIER_ROAD[6].z, count: 3 },
1886
+ ];
1273
1887
  /**
1274
1888
  * Cultivated cabbage plots on the soldier shore - between lake and camps.
1275
1889
  * Filled as neat soil rows (not sparse scatter).
@@ -1302,7 +1916,8 @@ export const DISCOVERY_ZONES = [
1302
1916
  // The id stays 'lil-beach' on purpose. Discovery is persisted per player
1303
1917
  // as a set of these ids, so renaming it would un-discover the starting
1304
1918
  // zone for every existing character and pay the XP out a second time.
1305
- // `SPAWN_ZONE_ID` and the 'lil-beach-safety' area effect match on it too.
1919
+ // `SPAWN_ZONE_ID` matches on it too. (The +10 Health area effect no longer
1920
+ // does: it keys off `mapAreaAt` and covers the whole Lower Kingdoms.)
1306
1921
  // Only the label is player-facing, and only the label changed.
1307
1922
  id: 'lil-beach',
1308
1923
  label: 'Hope Beach',
@@ -1363,7 +1978,7 @@ export const DISCOVERY_ZONES = [
1363
1978
  label: 'Northwest Battlefield',
1364
1979
  minX: BATTLEFIELD_MIN_X,
1365
1980
  maxX: BATTLEFIELD_MAX_X,
1366
- minZ: BATTLEFIELD_MIN_LOCAL_Z,
1981
+ minZ: toLocalZ(GOBLIN_FRONTIER_MIN_Z),
1367
1982
  maxZ: BATTLEFIELD_MAX_LOCAL_Z,
1368
1983
  },
1369
1984
  {
@@ -1397,6 +2012,26 @@ export const DISCOVERY_ZONES = [
1397
2012
  minZ: dreadwoodsNpcZone('spider-hollow').cz - dreadwoodsNpcZone('spider-hollow').rz - WORLD_NORTH_PAD,
1398
2013
  maxZ: dreadwoodsNpcZone('spider-hollow').cz + dreadwoodsNpcZone('spider-hollow').rz - WORLD_NORTH_PAD,
1399
2014
  },
2015
+ {
2016
+ // The Broken Road, from its descriptor like the hollow. The box is the
2017
+ // ellipse's extents plus one column east, so the card fires at the arch.
2018
+ id: 'broken-road',
2019
+ label: dreadwoodsNpcZone('broken-road').label,
2020
+ minX: dreadwoodsNpcZone('broken-road').cx - dreadwoodsNpcZone('broken-road').rx,
2021
+ maxX: dreadwoodsNpcZone('broken-road').cx + dreadwoodsNpcZone('broken-road').rx + 1,
2022
+ minZ: dreadwoodsNpcZone('broken-road').cz - dreadwoodsNpcZone('broken-road').rz - WORLD_NORTH_PAD,
2023
+ maxZ: dreadwoodsNpcZone('broken-road').cz + dreadwoodsNpcZone('broken-road').rz - WORLD_NORTH_PAD,
2024
+ },
2025
+ {
2026
+ // The dragon ground at the centre of the woods, from its descriptor like
2027
+ // the hollow and the road, so the card fires at the ring.
2028
+ id: 'blood-lake',
2029
+ label: dreadwoodsNpcZone('blood-lake').label,
2030
+ minX: dreadwoodsNpcZone('blood-lake').cx - dreadwoodsNpcZone('blood-lake').rx,
2031
+ maxX: dreadwoodsNpcZone('blood-lake').cx + dreadwoodsNpcZone('blood-lake').rx,
2032
+ minZ: dreadwoodsNpcZone('blood-lake').cz - dreadwoodsNpcZone('blood-lake').rz - WORLD_NORTH_PAD,
2033
+ maxZ: dreadwoodsNpcZone('blood-lake').cz + dreadwoodsNpcZone('blood-lake').rz - WORLD_NORTH_PAD,
2034
+ },
1400
2035
  {
1401
2036
  id: 'trout-fishery',
1402
2037
  label: 'Trout Fishery',
@@ -1418,6 +2053,16 @@ export const DISCOVERY_ZONES = [
1418
2053
  minZ: NORTHERN_GARRISON_MIN_Z - 2 - WORLD_NORTH_PAD,
1419
2054
  maxZ: NORTHERN_WALL_Z + 22 - WORLD_NORTH_PAD,
1420
2055
  },
2056
+ {
2057
+ // The cove, pier and hut Branson's ship puts in at. Listed before the
2058
+ // shore strip, which runs the whole west edge and would claim it.
2059
+ id: 'bransons-landing',
2060
+ label: "Branson's Landing",
2061
+ minX: BRANSONS_LANDING_WORKS.minX,
2062
+ maxX: BRANSONS_LANDING_WORKS.maxX,
2063
+ minZ: BRANSONS_LANDING_WORKS.minZ - WORLD_NORTH_PAD,
2064
+ maxZ: BRANSONS_LANDING_WORKS.maxZ - WORLD_NORTH_PAD,
2065
+ },
1421
2066
  {
1422
2067
  // The west edge of the snow, where the boats and jetties are iced in.
1423
2068
  id: 'frozen-shore',
@@ -1455,6 +2100,35 @@ export const DISCOVERY_ZONES = [
1455
2100
  minZ: FROZEN_HALLWAY_SEAL_Z - 2 - WORLD_NORTH_PAD,
1456
2101
  maxZ: FROZEN_HALLWAY_MOUTH_Z + 7 - WORLD_NORTH_PAD,
1457
2102
  },
2103
+ // The Capital's eight wards, each its own discovery; the castle ward is
2104
+ // listed first because its bounds overlap the temple and noble wards.
2105
+ ...[...CAPITAL_DISTRICTS].sort((a, b) => (a.id === 'capital-castle' ? -1 : b.id === 'capital-castle' ? 1 : 0)).map((d) => ({
2106
+ id: d.id,
2107
+ label: d.label,
2108
+ minX: d.minX,
2109
+ maxX: d.maxX,
2110
+ minZ: d.minZ - WORLD_NORTH_PAD,
2111
+ maxZ: d.maxZ - WORLD_NORTH_PAD,
2112
+ })),
2113
+ {
2114
+ // The walled capital of the northern kingdoms, moat and all.
2115
+ id: 'capital',
2116
+ label: 'The Capital',
2117
+ minX: CAPITAL_WORKS.minX,
2118
+ maxX: CAPITAL_WORKS.maxX,
2119
+ minZ: CAPITAL_WORKS.minZ - WORLD_NORTH_PAD,
2120
+ maxZ: CAPITAL_WORKS.maxZ - WORLD_NORTH_PAD,
2121
+ },
2122
+ {
2123
+ // Snow-to-green join east of the Frozen region, sea to the woods.
2124
+ // Listed before the snowfields, which would otherwise claim the west rim.
2125
+ id: 'thaw-gorge',
2126
+ label: 'The Thaw Gorge',
2127
+ minX: THAW_FLOOD_WEST_X - 8,
2128
+ maxX: THAW_FLOOD_EAST_X + 16,
2129
+ minZ: THAW_GORGE_MIN_Z - WORLD_NORTH_PAD,
2130
+ maxZ: DEADLANDS_MIN_Z - 1 - WORLD_NORTH_PAD,
2131
+ },
1458
2132
  {
1459
2133
  id: 'northern-snowfields',
1460
2134
  label: 'The Snowfields',
@@ -1483,12 +2157,23 @@ function discoveryZoneCenter(id) {
1483
2157
  z: toWorldZ(Math.round((zone.minZ + zone.maxZ) / 2)),
1484
2158
  };
1485
2159
  }
1486
- /** The mine's eastern Zone 2 location, shared by scenery, lighting, and the map. */
1487
- export const MINE_CENTER_X = DEADLANDS_MAX_X - 59;
2160
+ /**
2161
+ * The mine's eastern Zone 2 location, shared by scenery, lighting, and the map.
2162
+ * The yard's half-extents live here too, so map.ts, the terrain tint and the
2163
+ * lighting rig all agree on where the yard ends: it used to be 11x8 with three
2164
+ * separate copies of the number, and is 19x14 since 2026-09-16 (the same six
2165
+ * seams, spread out, with room to walk between them). The centre moved seven
2166
+ * columns west with the width so the east rim still sits inside Zone 2.
2167
+ */
2168
+ export const MINE_YARD_RX = 19;
2169
+ export const MINE_YARD_RZ = 14;
2170
+ export const MINE_CENTER_X = DEADLANDS_MAX_X - 66;
1488
2171
  export const MINE_CENTER_Z = DEADLANDS_MAX_Z - 65;
1489
- export const MINE_HEAD_Z = MINE_CENTER_Z - 5;
2172
+ /** The head frame stands two tiles inside the north rim, backed by the rock. */
2173
+ export const MINE_HEAD_Z = MINE_CENTER_Z - MINE_YARD_RZ + 2;
1490
2174
  export function inMineYard(x, z, margin = 0) {
1491
- return Math.abs(x - MINE_CENTER_X) <= 13 + margin && Math.abs(z - MINE_CENTER_Z) <= 10 + margin;
2175
+ return (Math.abs(x - MINE_CENTER_X) <= MINE_YARD_RX + 2 + margin &&
2176
+ Math.abs(z - MINE_CENTER_Z) <= MINE_YARD_RZ + 2 + margin);
1492
2177
  }
1493
2178
  /**
1494
2179
  * Paper-map labels. Keep this list in lockstep with named places in this file
@@ -1500,9 +2185,14 @@ export const WORLD_MAP_LANDMARKS = [
1500
2185
  { x: NORTHERN_GATE_X, z: NORTHERN_WALL_Z, label: 'Northern Gate' },
1501
2186
  { x: NORTHERN_CAMP_CENTER.x, z: NORTHERN_CAMP_CENTER.z, label: 'War Camp', labelSide: 'right' },
1502
2187
  { x: 8, z: 200, label: 'Frozen Shore', labelSide: 'right' },
2188
+ { x: BRANSONS_LANDING_SHIP.x, z: BRANSONS_LANDING_PIER_MAX_Z, label: "Branson's Landing", labelSide: 'below' },
1503
2189
  { x: FROZEN_LAKE_CENTER.x, z: FROZEN_LAKE_CENTER.z, label: 'Frozen Lake', labelSide: 'right' },
1504
2190
  { x: CRYSTAL_BRIDGE_X, z: (CRYSTAL_GORGE_MIN_Z + CRYSTAL_GORGE_MAX_Z) / 2, label: 'Crystal Bridge', labelSide: 'right' },
1505
2191
  { x: FROZEN_HALLWAY_X, z: (FROZEN_HALLWAY_GATE_Z + FROZEN_HALLWAY_MOUTH_Z) / 2, label: 'Frozen Hallway', labelSide: 'right' },
2192
+ { x: THAW_GORGE_CX, z: THAW_BRIDGE_Z, label: 'Thaw Bridge', labelSide: 'right' },
2193
+ { x: THAW_GORGE_CX, z: THAW_FALLS_Z, label: 'Thaw Fall', labelSide: 'right' },
2194
+ { x: THAW_GORGE_CX, z: THAW_MOUTH_STAND_Z, label: 'Thaw Mouth', labelSide: 'right' },
2195
+ { x: CAPITAL_KEEP.x - 110, z: CAPITAL_KEEP.z, label: 'The Capital', labelSide: 'above' },
1506
2196
  { ...discoveryZoneCenter('no-harm-farm'), label: 'Farm' },
1507
2197
  { x: CROSSROADS_WORLD_X, z: toWorldZ(CROSSROADS_LOCAL_Z), label: 'Crossroads' },
1508
2198
  { ...discoveryZoneCenter('henson-pond'), label: 'Pond' },
@@ -1522,11 +2212,12 @@ export const WORLD_MAP_LANDMARKS = [
1522
2212
  { ...discoveryZoneCenter('basecamp'), label: 'Base Camp', labelSide: 'left' },
1523
2213
  {
1524
2214
  x: BATTLEFIELD_BRIDGE_X,
1525
- z: Math.round((BATTLEFIELD_MIN_WORLD_Z + SNOW_EDGE_Z) / 2),
2215
+ z: Math.round((GOBLIN_FRONTIER_MIN_Z + GOBLIN_FRONTIER_MAX_Z) / 2),
1526
2216
  label: 'Battlefield',
1527
2217
  labelSide: 'above',
1528
2218
  },
1529
2219
  { x: TOWN_GATE_X, z: TOWN_CROSS_STREET_Z, label: 'Calverton' },
2220
+ { x: BLOOD_LAKE_CX, z: BLOOD_LAKE_CZ, label: 'Blood Lake', labelSide: 'on' },
1530
2221
  { x: SPIDER_CAVE_MOUTH_X, z: SPIDER_CAVE_MOUTH_Z, label: 'Dungeon', labelSide: 'left' },
1531
2222
  {
1532
2223
  x: BASECAMP_CAVE_X,
@@ -1542,7 +2233,7 @@ export const WORLD_MAP_LANDMARKS = [
1542
2233
  * The corridor test is generous (lane + tree screens + arrival clearing + a
1543
2234
  * margin), so wildlife cannot spawn on the path a player walks up. The second
1544
2235
  * test claims everything north of the lake and east of the Dreadwoods, which is
1545
- * reachable only by the trail causeway or the Dreadwoods east gate.
2236
+ * reachable only by the trail and its bridge or the Dreadwoods east gate.
1546
2237
  */
1547
2238
  export function isEastArea(x, z) {
1548
2239
  // Overworld only. The dungeon, the goblin cave and the forest dens all live
@@ -1559,10 +2250,16 @@ export function isEastArea(x, z) {
1559
2250
  return true;
1560
2251
  }
1561
2252
  // Open pad north of the Battlefield lake and east of the Dreadwoods - sealed
1562
- // off by the lake, reachable only by the trail causeway.
2253
+ // off by the lake, reachable only over the Calverton bridge.
1563
2254
  if (z <= BATTLEFIELD_LAKE_MIN_Z - 1 && x >= DEADLANDS_MAX_X + DEADLANDS_DEFENSE_DEPTH + 1) {
1564
2255
  return true;
1565
2256
  }
2257
+ // The bridge itself. Its deck is over water no other clause claims, and a
2258
+ // player is East on the south bank and East on the north bank; without this
2259
+ // the Lower Kingdoms blessing would switch on at the first plank and off at
2260
+ // the last, with a notice each way.
2261
+ if (inCalvertonBridge(x, z))
2262
+ return true;
1566
2263
  // The eastern nub south of the lake, east of Base Camp's rim: only reached by
1567
2264
  // leaving the tunnel or the long way round the camp mound. Stops short of the
1568
2265
  // beach, which is South.
@@ -1597,18 +2294,19 @@ export const MINE_YARD_HEIGHT = 1;
1597
2294
  /**
1598
2295
  * Where each seam stands, as an offset from the mine centre.
1599
2296
  *
1600
- * Three copper and one tin. They sit against the worked face on the north and
1601
- * east rim rather than out in the yard, and no two are adjacent, so a pair of
1602
- * players can work neighbouring rocks without blocking each other's tile.
2297
+ * Three copper, one tin, two iron: the same six as before the yard grew, now
2298
+ * spread around the worked faces on the west, north and east rims with five
2299
+ * or more tiles between any two, so the yard is walked rather than stood in
2300
+ * and a pair of players never share a rock's tile.
1603
2301
  */
1604
2302
  export const MINE_SEAMS = [
1605
- { dx: -7, dz: -5, style: 'copper' },
1606
- { dx: -8, dz: -1, style: 'copper' },
1607
- { dx: -5, dz: -7, style: 'copper' },
2303
+ { dx: -16, dz: -9, style: 'copper' },
2304
+ { dx: -17, dz: 1, style: 'copper' },
2305
+ { dx: -9, dz: -12, style: 'copper' },
1608
2306
  // The single tin seam works the eastern side of the face.
1609
- { dx: 7, dz: -5, style: 'tin' },
2307
+ { dx: 14, dz: -10, style: 'tin' },
1610
2308
  // Iron seams along the deep northern and eastern faces.
1611
- { dx: 5, dz: -7, style: 'iron' },
1612
- { dx: 8, dz: -1, style: 'iron' },
2309
+ { dx: 7, dz: -12, style: 'iron' },
2310
+ { dx: 17, dz: -2, style: 'iron' },
1613
2311
  ];
1614
2312
  //# sourceMappingURL=landmarks.js.map