@dreadwood-types/types 0.1.30 → 0.1.43

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 (64) hide show
  1. package/dist/adventure.d.ts +49 -0
  2. package/dist/adventure.d.ts.map +1 -0
  3. package/dist/adventure.js +10 -0
  4. package/dist/adventure.js.map +1 -0
  5. package/dist/areaTransitions.d.ts +34 -0
  6. package/dist/areaTransitions.d.ts.map +1 -0
  7. package/dist/areaTransitions.js +23 -0
  8. package/dist/areaTransitions.js.map +1 -0
  9. package/dist/blacksmithing.d.ts +32 -0
  10. package/dist/blacksmithing.d.ts.map +1 -0
  11. package/dist/blacksmithing.js +207 -0
  12. package/dist/blacksmithing.js.map +1 -0
  13. package/dist/clientConfig.js +1 -1
  14. package/dist/clientConfig.js.map +1 -1
  15. package/dist/constants.d.ts +129 -4
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/constants.js +254 -5
  18. package/dist/constants.js.map +1 -1
  19. package/dist/cooking.d.ts.map +1 -1
  20. package/dist/cooking.js +5 -0
  21. package/dist/cooking.js.map +1 -1
  22. package/dist/dreadwoodsZones.d.ts +74 -0
  23. package/dist/dreadwoodsZones.d.ts.map +1 -0
  24. package/dist/dreadwoodsZones.js +101 -0
  25. package/dist/dreadwoodsZones.js.map +1 -0
  26. package/dist/equipment.d.ts +3 -0
  27. package/dist/equipment.d.ts.map +1 -1
  28. package/dist/equipment.js +24 -0
  29. package/dist/equipment.js.map +1 -1
  30. package/dist/groundItems.d.ts.map +1 -1
  31. package/dist/groundItems.js +12 -0
  32. package/dist/groundItems.js.map +1 -1
  33. package/dist/index.d.ts +4 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +4 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/landmarks.d.ts +395 -19
  38. package/dist/landmarks.d.ts.map +1 -1
  39. package/dist/landmarks.js +548 -80
  40. package/dist/landmarks.js.map +1 -1
  41. package/dist/messages.d.ts +72 -6
  42. package/dist/messages.d.ts.map +1 -1
  43. package/dist/messages.js +8 -0
  44. package/dist/messages.js.map +1 -1
  45. package/dist/mining.d.ts +16 -11
  46. package/dist/mining.d.ts.map +1 -1
  47. package/dist/mining.js +54 -15
  48. package/dist/mining.js.map +1 -1
  49. package/dist/quests.d.ts +2 -2
  50. package/dist/quests.d.ts.map +1 -1
  51. package/dist/quests.js +30 -2
  52. package/dist/quests.js.map +1 -1
  53. package/dist/spiderCave.d.ts +17 -3
  54. package/dist/spiderCave.d.ts.map +1 -1
  55. package/dist/spiderCave.js +25 -3
  56. package/dist/spiderCave.js.map +1 -1
  57. package/dist/woodwork.d.ts +118 -0
  58. package/dist/woodwork.d.ts.map +1 -1
  59. package/dist/woodwork.js +178 -4
  60. package/dist/woodwork.js.map +1 -1
  61. package/dist/zones.d.ts.map +1 -1
  62. package/dist/zones.js +9 -2
  63. package/dist/zones.js.map +1 -1
  64. package/package.json +3 -2
package/dist/landmarks.js CHANGED
@@ -1,11 +1,12 @@
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, toWorldZ, } from './constants.js';
2
- import { DEADLANDS_DEFENSE_DEPTH, DEADLANDS_MAX_X, DEADLANDS_MAX_Z, DEADLANDS_MIN_X, } from './zones.js';
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';
3
3
  import { SNOW_DEEP_Z, SNOW_EDGE_Z } from './snow.js';
4
+ import { dreadwoodsNpcZone } from './dreadwoodsZones.js';
4
5
  export const CROSSROADS_WORLD_X = BEACH_GATE_X;
5
6
  export const CROSSROADS_LOCAL_Z = 17;
6
7
  /**
7
8
  * Bounty Board and Bruce - on the south side of the Crossroads, the first thing a
8
- * player meets coming up the road from Lil Beach. Inside the existing `crossroads`
9
+ * player meets coming up the road from Hope Beach. Inside the existing `crossroads`
9
10
  * discovery zone; no new zone.
10
11
  */
11
12
  export const BOUNTY_BOARD_X = CROSSROADS_WORLD_X + 3;
@@ -13,6 +14,129 @@ export const BOUNTY_BOARD_LOCAL_Z = CROSSROADS_LOCAL_Z + 3;
13
14
  /** Bruce stands two tiles west of the board, clear of its posts rather than tucked against them. */
14
15
  export const BOUNTY_BRUCE_X = CROSSROADS_WORLD_X + 1;
15
16
  export const BOUNTY_BRUCE_LOCAL_Z = CROSSROADS_LOCAL_Z + 3;
17
+ /* --- The Long Pier, Hope Beach -------------------------------------------
18
+ *
19
+ * A single `brokenpier` prop used to stand on the sand here: three tiles of
20
+ * weathered planking that read as scenery and nothing else. The pier is now a
21
+ * tiled deck like the battlefield bridge, running the full depth of the ocean
22
+ * strip, with two smashed spans in it that a player planks over one at a time.
23
+ *
24
+ * All world coordinates. The deck is Path at height 1 over Water, so it stands
25
+ * proud of the sea the same way the bridge stands proud of the lake.
26
+ */
27
+ export const HOPE_PIER_X = BEACH_GATE_X + 28;
28
+ /** Tiles either side of HOPE_PIER_X. Three wide, one wider than the bridge. */
29
+ export const HOPE_PIER_HALF_W = 1;
30
+ /** Sand ramp where the deck leaves the beach. */
31
+ export const HOPE_PIER_SHORE_Z = OCEAN_START_Z - 3;
32
+ /**
33
+ * Seaward end. Pinned to the shore rather than to the map edge: the south sea
34
+ * pad exists to put open water BEYOND the pier head (see WORLD_SOUTH_CHUNKS),
35
+ * and tying the end to WORLD_HEIGHT would spend that padding on more decking
36
+ * and leave the boundary visible from the last plank.
37
+ */
38
+ export const HOPE_PIER_END_Z = OCEAN_START_Z + 87;
39
+ /**
40
+ * The east branch: a spur off the main walk to the moored ship, deliberately
41
+ * SHORE-SIDE of both smashed spans so the ship is open to everybody, repaired
42
+ * pier or not.
43
+ */
44
+ export const HOPE_PIER_BRANCH_Z = OCEAN_START_Z + 14;
45
+ export const HOPE_PIER_BRANCH_HALF_W = 1;
46
+ export const HOPE_PIER_BRANCH_MIN_X = HOPE_PIER_X + HOPE_PIER_HALF_W + 1;
47
+ export const HOPE_PIER_BRANCH_MAX_X = HOPE_PIER_X + 12;
48
+ /**
49
+ * First smashed span, about a third of the way out - far enough that the walk
50
+ * to it is a walk, close enough to see the break from the sand.
51
+ */
52
+ export const HOPE_PIER_GAP1_MIN_Z = OCEAN_START_Z + 28;
53
+ export const HOPE_PIER_GAP1_MAX_Z = OCEAN_START_Z + 29;
54
+ /** Second smashed span, out past the middle deck at about two thirds. */
55
+ export const HOPE_PIER_GAP2_MIN_Z = OCEAN_START_Z + 58;
56
+ export const HOPE_PIER_GAP2_MAX_Z = OCEAN_START_Z + 59;
57
+ /**
58
+ * The pier head: the last four rows widen into a platform, so the end of a long
59
+ * walk is somewhere to stand rather than the last plank of a corridor.
60
+ */
61
+ export const HOPE_PIER_HEAD_MIN_Z = HOPE_PIER_END_Z - 3;
62
+ /** The middle deck, a passing place between the two smashed spans. */
63
+ export const HOPE_PIER_MID_MIN_Z = OCEAN_START_Z + 42;
64
+ export const HOPE_PIER_MID_MAX_Z = OCEAN_START_Z + 44;
65
+ /** Both widenings are five tiles across against the walk's three. */
66
+ export const HOPE_PIER_WIDE_HALF_W = 2;
67
+ /** The moored ship, off the seaward corner of the branch. */
68
+ export const HOPE_PIER_SHIP_X = HOPE_PIER_BRANCH_MAX_X + 4;
69
+ export const HOPE_PIER_SHIP_Z = HOPE_PIER_BRANCH_Z;
70
+ /**
71
+ * Saul's tackle pitch: on the sand beside the pier ramp, west of the lane so
72
+ * he never stands in the way of the walk out. He faces the pier.
73
+ */
74
+ export const SAUL_SPAWN_X = HOPE_PIER_X - HOPE_PIER_HALF_W - 2;
75
+ /**
76
+ * Well up the dry sand, not on the last row before the ocean. The tile one row
77
+ * off the ramp is beach by the grid but the rendered shoreline laps across it,
78
+ * so he read as standing in the sea.
79
+ */
80
+ export const SAUL_SPAWN_Z = HOPE_PIER_SHORE_Z - 3;
81
+ /**
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
+ ];
93
+ /** Tiles either side of the centre line at this row. Wider over the two decks. */
94
+ export function hopePierHalfWidthAt(z) {
95
+ if (z >= HOPE_PIER_HEAD_MIN_Z)
96
+ return HOPE_PIER_WIDE_HALF_W;
97
+ if (z >= HOPE_PIER_MID_MIN_Z && z <= HOPE_PIER_MID_MAX_Z)
98
+ return HOPE_PIER_WIDE_HALF_W;
99
+ return HOPE_PIER_HALF_W;
100
+ }
101
+ /** The east spur out to the moored ship. */
102
+ 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);
106
+ }
107
+ export function onHopePierLane(x, z) {
108
+ if (onHopePierBranch(x, z))
109
+ return true;
110
+ return (z >= HOPE_PIER_SHORE_Z &&
111
+ z <= HOPE_PIER_END_Z &&
112
+ Math.abs(x - HOPE_PIER_X) <= hopePierHalfWidthAt(z));
113
+ }
114
+ /**
115
+ * The one tile of a smashed span that carries the work, and once planked the
116
+ * crossing icon. Centre of the break's landward row - the same discipline as
117
+ * isBattlefieldBridgeTravelIconTile, so the hammer and the portal share a tile.
118
+ */
119
+ export function isHopePierTravelIconTile(x, z) {
120
+ if (x !== HOPE_PIER_X)
121
+ return false;
122
+ return z === HOPE_PIER_GAP1_MIN_Z || z === HOPE_PIER_GAP2_MIN_Z;
123
+ }
124
+ /** Where a player lands after using a planked span's crossing icon. */
125
+ export function hopePierCrossingExitZ(gap, fromZ) {
126
+ const minZ = gap === 1 ? HOPE_PIER_GAP1_MIN_Z : HOPE_PIER_GAP2_MIN_Z;
127
+ const maxZ = gap === 1 ? HOPE_PIER_GAP1_MAX_Z : HOPE_PIER_GAP2_MAX_Z;
128
+ return fromZ <= minZ ? maxZ + 2 : minZ - 2;
129
+ }
130
+ /** Which smashed span a tile belongs to, or null for sound deck / open sea. */
131
+ export function hopePierGapAt(x, z) {
132
+ if (Math.abs(x - HOPE_PIER_X) > HOPE_PIER_HALF_W)
133
+ return null;
134
+ if (z >= HOPE_PIER_GAP1_MIN_Z && z <= HOPE_PIER_GAP1_MAX_Z)
135
+ return 1;
136
+ if (z >= HOPE_PIER_GAP2_MIN_Z && z <= HOPE_PIER_GAP2_MAX_Z)
137
+ return 2;
138
+ return null;
139
+ }
16
140
  export const EAST_ROAD_START_X = CROSSROADS_WORLD_X + 7;
17
141
  export const HIJACK_SITE_X = 402;
18
142
  export const HIJACK_SITE_LOCAL_Z = 22;
@@ -36,6 +160,20 @@ export const BASECAMP_SLOPE_MIN_X = 605;
36
160
  export const BASECAMP_SLOPE_MAX_X = 626;
37
161
  export const BASECAMP_CAVE_X = BASECAMP_CENTER_X + Math.round(BASECAMP_RX * 0.76);
38
162
  export const BASECAMP_CAVE_LOCAL_Z = BASECAMP_CENTER_LOCAL_Z;
163
+ /**
164
+ * The Suspicious hole gets its own style so it can keep its own entrance - the
165
+ * carved stone gate in the camp's rock face. It used to render whatever the
166
+ * shared default was, so when that default became the rat-pit mound on
167
+ * 2026-09-06 the gate went with it. Any mouth left on a bare compass style
168
+ * (the far-side door) keeps the mound; this one does not.
169
+ *
170
+ * The compass suffix still carries the facing: `caveEntranceFacing` and the
171
+ * client's FACING_YAW both read the direction out of the style string.
172
+ */
173
+ export const BASECAMP_CAVE_MOUTH_STYLE = 'basecamp-west';
174
+ export function isBaseCampCaveMouthStyle(style) {
175
+ return typeof style === 'string' && style.startsWith('basecamp-');
176
+ }
39
177
  /** Small lookout beyond the Base Camp tunnel, reached only after clearing Livonia. */
40
178
  export const BASECAMP_FAR_SIDE_X = BASECAMP_CAVE_X + 30;
41
179
  export const BASECAMP_FAR_SIDE_LOCAL_Z = BASECAMP_CAVE_LOCAL_Z;
@@ -179,6 +317,12 @@ export function inDreadwoodsEastGate(x, z) {
179
317
  return (x === DREADWOODS_EAST_GATE_X &&
180
318
  Math.abs(z - DREADWOODS_EAST_GATE_Z) <= DREADWOODS_EAST_GATE_HALF_W);
181
319
  }
320
+ export const DREADWOODS_SOUTH_GATE_HALF_W = 2;
321
+ /** The south gate opening itself - the tiles cut out of the south wall column. */
322
+ export function inDreadwoodsSouthGate(x, z) {
323
+ return (z === DEADLANDS_MAX_Z &&
324
+ Math.abs(x - DEADLANDS_GATE_X) <= DREADWOODS_SOUTH_GATE_HALF_W);
325
+ }
182
326
  /** The gate road plus a margin, for keeping den mouths and wild scatter off it. */
183
327
  export function nearDreadwoodsEastRoad(x, z, margin = 6) {
184
328
  return (x >= DREADWOODS_EAST_ROAD_MIN_X - margin &&
@@ -484,14 +628,15 @@ export function inDungeonZone(x, z) {
484
628
  */
485
629
  export const EXPLORER_IDENTIFY_XP = 3;
486
630
  export const EXPLORER_DISCOVERY_XP = 33;
487
- /** Lil Beach is "discovered" the instant you log in, so it pays a token amount. */
631
+ /** Hope Beach is "discovered" the instant you log in, so it pays a token amount. */
488
632
  export const SPAWN_ZONE_DISCOVERY_XP = 3;
489
- /** Tight animal-dug hollow - a handful of tiles, not a mystery hall. */
490
- export const FOREST_DEN_RX = 4;
491
- export const FOREST_DEN_RZ = 3;
633
+ /** Rat warren with a central nest and two smaller feeding alcoves. */
634
+ export const FOREST_DEN_RX = 8;
635
+ export const FOREST_DEN_RZ = 7;
492
636
  export const FOREST_DEN_WALL = 2;
493
637
  export const FOREST_DEN_EXPLORER_XP = 33;
494
- const FOREST_DEN_CAVERN_X = UNDERGROUND_ORIGIN_X + FOREST_DEN_RX + FOREST_DEN_WALL + 4;
638
+ // Keep the original centres so existing characters remain in their own den.
639
+ const FOREST_DEN_CAVERN_X = UNDERGROUND_ORIGIN_X + 10;
495
640
  const FOREST_DEN_CAVERN_Z0 = 108;
496
641
  const FOREST_DEN_CAVERN_GAP = 20;
497
642
  function forestDenSite(id, entranceX, entranceLocalZ, facing, cavernIndex) {
@@ -567,6 +712,9 @@ export function forestDenByCavern(worldX, worldZ) {
567
712
  export function inForestDenCavern(x, z) {
568
713
  return forestDenByCavern(x, z) !== null;
569
714
  }
715
+ /** Wolf hollows retain their original compact footprint. */
716
+ export const WOLF_DEN_RX = 4;
717
+ export const WOLF_DEN_RZ = 3;
570
718
  const WOLF_DEN_CAVERN_X = UNDERGROUND_ORIGIN_X + 70;
571
719
  const WOLF_DEN_CAVERN_Z0 = 40;
572
720
  const WOLF_DEN_CAVERN_GAP = 18;
@@ -636,8 +784,8 @@ export function wolfDenByEntrance(worldX, worldZ) {
636
784
  }
637
785
  export function wolfDenByCavern(worldX, worldZ) {
638
786
  for (const den of WOLF_DEN_SITES) {
639
- if (Math.abs(worldX - den.cavernX) <= FOREST_DEN_RX + FOREST_DEN_WALL &&
640
- Math.abs(worldZ - den.cavernZ) <= FOREST_DEN_RZ + FOREST_DEN_WALL) {
787
+ if (Math.abs(worldX - den.cavernX) <= WOLF_DEN_RX + FOREST_DEN_WALL &&
788
+ Math.abs(worldZ - den.cavernZ) <= WOLF_DEN_RZ + FOREST_DEN_WALL) {
641
789
  return den;
642
790
  }
643
791
  }
@@ -677,40 +825,58 @@ function giantRatPitSpawnSpots() {
677
825
  ];
678
826
  return offsets.map(([dx, dz]) => ({ x: cx + dx, z: pz + dz }));
679
827
  }
680
- /** Dense pack of ordinary rats in the Amazonian cave guardroom (room 1). */
828
+ /** Swarming horde of tunnel vermin across the sunken rat pit (room 1). */
681
829
  function room1RatSpawnSpots() {
682
830
  const cx = DUNGEON_CENTER_X;
683
- const minZ = DUNGEON_ROOM1_MIN_Z;
684
- const maxZ = DUNGEON_ROOM1_MAX_Z;
685
- // [dx, depth fraction] - keep clear of the wall ring (±12) and gate approaches.
831
+ // [dx, z] - well distributed across north nooks, 4 corner alcoves,
832
+ // mud puddles, center feeding mound, and perimeter walkways.
833
+ const offsets = [
834
+ // North nooks and throat (z=63-64)
835
+ [-10, 64], [-8, 63], [-6, 64], [-4, 63], [4, 63], [6, 64], [8, 63], [10, 64],
836
+ // NW Alcove and rat nest (z=65-67)
837
+ [-14, 65], [-14, 67], [-12, 66], [-11, 65],
838
+ // NE Alcove and rat nest (z=65-67)
839
+ [14, 65], [14, 67], [12, 66], [11, 65],
840
+ // Upper mid and arrival flank (z=65-68)
841
+ [-9, 66], [-5, 65], [5, 65], [9, 66],
842
+ [-7, 68], [-3, 67], [3, 67], [7, 68],
843
+ // Center feast and sunken pit ring (z=69-73)
844
+ [-10, 70], [-6, 69], [-2, 69], [2, 69], [6, 69], [10, 70],
845
+ [-9, 72], [-5, 71], [-1, 72], [1, 71], [5, 72], [9, 71],
846
+ [-7, 73], [-3, 73], [3, 73], [7, 73],
847
+ // SW Alcove and mud puddle skeleton (z=75-77)
848
+ [-14, 75], [-14, 77], [-12, 75], [-11, 77],
849
+ // SE Alcove and trophy caches (z=75-77)
850
+ [14, 75], [14, 77], [12, 75], [11, 77],
851
+ // Lower pit and south mud puddle (z=75-78)
852
+ [-8, 75], [-4, 75], [-2, 77], [2, 76], [6, 76], [8, 75],
853
+ [-9, 78], [-5, 78], [5, 78], [9, 78],
854
+ ];
855
+ return offsets.map(([dx, z]) => ({ x: cx + dx, z }));
856
+ }
857
+ /** Denser garrison for the Amazonian Cave in Room 3 of the Rat Tunnel. */
858
+ function amazonHallSpawnSpots() {
859
+ const cx = DUNGEON_CENTER_X;
686
860
  const offsets = [
687
- [-9, 0.22],
688
- [0, 0.22],
689
- [9, 0.22],
690
- [-5, 0.32],
691
- [5, 0.32],
692
- [-9, 0.42],
693
- [0, 0.42],
694
- [9, 0.42],
695
- [-5, 0.52],
696
- [5, 0.52],
697
- [-9, 0.62],
698
- [0, 0.62],
699
- [9, 0.62],
700
- [-5, 0.72],
701
- [5, 0.72],
702
- [0, 0.82],
861
+ // North hall approach
862
+ [-4, 146], [3, 146],
863
+ [-9, 149], [8, 149],
864
+ [0, 150],
865
+ // Mid hall flanks
866
+ [-4, 153], [3, 153],
867
+ [-8, 158], [8, 158],
868
+ // South camp clearing
869
+ [-4, 162], [4, 162],
870
+ [-8, 165], [8, 165],
871
+ [0, 166],
703
872
  ];
704
- return offsets.map(([dx, t]) => ({
705
- x: cx + dx,
706
- z: Math.round(minZ + (maxZ - minZ) * t),
707
- }));
873
+ return offsets.map(([dx, z]) => ({ x: cx + dx, z }));
708
874
  }
709
875
  /** Fixed, deterministic spawn spots for each dungeon room's enemy group. */
710
876
  export const DUNGEON_ROOM_SPAWNS = {
711
877
  1: room1RatSpawnSpots(),
712
878
  2: giantRatPitSpawnSpots(),
713
- 3: roomSpawnSpots(DUNGEON_ROOM3_MIN_Z, DUNGEON_ROOM3_MAX_Z, 5),
879
+ 3: amazonHallSpawnSpots(),
714
880
  4: roomSpawnSpots(DUNGEON_ROOM4_MIN_Z, DUNGEON_ROOM4_MAX_Z, 1),
715
881
  };
716
882
  /**
@@ -719,11 +885,31 @@ export const DUNGEON_ROOM_SPAWNS = {
719
885
  * East edge meets the Deadlands west border. The lake continues east of this pad
720
886
  * (see BATTLEFIELD_LAKE_EAST_X).
721
887
  */
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;
722
908
  export const BATTLEFIELD_MIN_X = 10;
723
909
  export const BATTLEFIELD_MAX_X = DEADLANDS_MIN_X - 1;
724
- export const BATTLEFIELD_MIN_WORLD_Z = 648;
725
- // Includes the Captain's command camp at z=734 and its broad parade ground.
726
- export const BATTLEFIELD_MAX_WORLD_Z = 748;
910
+ export const BATTLEFIELD_MIN_WORLD_Z = bfZ(648);
911
+ // Includes the Captain's command camp and its broad parade ground.
912
+ export const BATTLEFIELD_MAX_WORLD_Z = bfZ(748);
727
913
  export const BATTLEFIELD_MIN_LOCAL_Z = BATTLEFIELD_MIN_WORLD_Z - WORLD_NORTH_PAD;
728
914
  export const BATTLEFIELD_MAX_LOCAL_Z = BATTLEFIELD_MAX_WORLD_Z - WORLD_NORTH_PAD;
729
915
  /**
@@ -733,8 +919,8 @@ export const BATTLEFIELD_MAX_LOCAL_Z = BATTLEFIELD_MAX_WORLD_Z - WORLD_NORTH_PAD
733
919
  * corridor south of the Deadlands, past the west-face enclave, until it meets
734
920
  * the SE high-rock row.
735
921
  */
736
- export const BATTLEFIELD_LAKE_MIN_Z = 666;
737
- export const BATTLEFIELD_LAKE_MAX_Z = 698;
922
+ export const BATTLEFIELD_LAKE_MIN_Z = bfZ(666);
923
+ export const BATTLEFIELD_LAKE_MAX_Z = bfZ(698);
738
924
  /** Solid southward wall returns join both Dreadwoods corners to the lake bank. */
739
925
  export const DEADLANDS_SOUTH_WALL_XS = [DEADLANDS_MIN_X, DEADLANDS_MAX_X];
740
926
  export const DEADLANDS_SOUTH_WALL_END_Z = BATTLEFIELD_LAKE_MIN_Z - 1;
@@ -777,12 +963,195 @@ export const BATTLEFIELD_CONTESTED_MID_Z = BATTLEFIELD_LAKE_MID_Z;
777
963
  * lengthens the water sheet without sliding the crossing.
778
964
  */
779
965
  export const BATTLEFIELD_BRIDGE_X = 91;
780
- /** Snowy frontier above the goblin camps, sealed west to the coastal cliff. */
781
- export const NORTHERN_WALL_Z = SNOW_DEEP_Z;
966
+ /**
967
+ * Snowy frontier above the goblin camps, sealed west to the coastal cliff.
968
+ * Sits north of SNOW_DEEP_Z so the gate and approach stay in solid snow rather
969
+ * than the green fleck band at the climate edge.
970
+ */
971
+ export const NORTHERN_WALL_Z = SNOW_DEEP_Z - 25;
782
972
  export const NORTHERN_WALL_MIN_X = 4;
783
973
  export const NORTHERN_WALL_MAX_X = DEADLANDS_MIN_X - 1;
784
974
  export const NORTHERN_GATE_X = BATTLEFIELD_BRIDGE_X;
785
975
  export const NORTHERN_GATE_HALF_WIDTH = 2;
976
+ /**
977
+ * The garrison camp on the north side of the gate: tents, a fire, stores, and
978
+ * the quartermaster. It is the only settled ground in the snow and the reason
979
+ * to walk through the gate at all. A shared clearing east of the road holds
980
+ * the tents and hearth; NORTHERN_GARRISON_X/Z is the quartermaster's counter.
981
+ */
982
+ export const NORTHERN_CAMP_CENTER = { x: NORTHERN_GATE_X + 23, z: NORTHERN_WALL_Z - 24 };
983
+ export const NORTHERN_GARRISON_X = NORTHERN_CAMP_CENTER.x - 9;
984
+ export const NORTHERN_GARRISON_Z = NORTHERN_CAMP_CENTER.z - 4;
985
+ /** Open approaches to the services around one communal camp. */
986
+ export const NORTHERN_CAMP_STATIONS = {
987
+ trading: { x: NORTHERN_CAMP_CENTER.x - 8, z: NORTHERN_CAMP_CENTER.z - 4, radius: 5 },
988
+ hearth: { x: NORTHERN_CAMP_CENTER.x, z: NORTHERN_CAMP_CENTER.z + 2, radius: 5 },
989
+ hunting: { x: NORTHERN_CAMP_CENTER.x + 6, z: NORTHERN_CAMP_CENTER.z - 4, radius: 5 },
990
+ guards: { x: NORTHERN_CAMP_CENTER.x + 9, z: NORTHERN_CAMP_CENTER.z + 5, radius: 5 },
991
+ travellers: { x: NORTHERN_CAMP_CENTER.x - 7, z: NORTHERN_CAMP_CENTER.z + 6, radius: 5 },
992
+ };
993
+ export const NORTHERN_CAMP_BOARD = { x: NORTHERN_CAMP_CENTER.x - 14, z: NORTHERN_CAMP_CENTER.z - 2 };
994
+ export const NORTHERN_CAMP_DWARVES = {
995
+ rolo: { x: NORTHERN_CAMP_CENTER.x - 3, z: NORTHERN_CAMP_CENTER.z + 1 },
996
+ bram: { x: NORTHERN_CAMP_CENTER.x + 5, z: NORTHERN_CAMP_CENTER.z - 5 },
997
+ tova: { x: NORTHERN_CAMP_CENTER.x + 2, z: NORTHERN_CAMP_CENTER.z + 3 },
998
+ };
999
+ export const NORTHERN_CAMP_ROAD_MIN_Z = NORTHERN_WALL_Z - 66;
1000
+ /** Camp bounds include its entrance lane; the public north road stays open. */
1001
+ export const NORTHERN_GARRISON_MIN_X = NORTHERN_GATE_X - 2;
1002
+ export const NORTHERN_GARRISON_MAX_X = NORTHERN_CAMP_CENTER.x + 18;
1003
+ export const NORTHERN_GARRISON_MIN_Z = NORTHERN_CAMP_CENTER.z - 15;
1004
+ export const NORTHERN_GARRISON_MAX_Z = NORTHERN_CAMP_CENTER.z + 15;
1005
+ /** Distance outside the central clearing and its five-tile entrance lane. */
1006
+ export function northernCampEdgeDistance(x, z) {
1007
+ const clearing = (Math.hypot((x - NORTHERN_CAMP_CENTER.x) / 17, (z - NORTHERN_CAMP_CENTER.z) / 14) - 1) * 14;
1008
+ const nearestX = Math.max(NORTHERN_GATE_X, Math.min(NORTHERN_CAMP_CENTER.x, x));
1009
+ return Math.min(clearing, Math.hypot(x - nearestX, z - NORTHERN_CAMP_CENTER.z) - 2);
1010
+ }
1011
+ export function inNorthernGarrison(x, z) {
1012
+ return (x >= NORTHERN_GARRISON_MIN_X &&
1013
+ x <= NORTHERN_GARRISON_MAX_X &&
1014
+ z >= NORTHERN_GARRISON_MIN_Z &&
1015
+ z <= NORTHERN_GARRISON_MAX_Z &&
1016
+ northernCampEdgeDistance(x, z) <= 0);
1017
+ }
1018
+ /**
1019
+ * The frozen lake north-west of the War Camp, out on the flat lowland short of
1020
+ * the Frozen Shore: holes cut through the ice, two fishers on their stools, a
1021
+ * jetty iced in at the east bank and a small shelter where the snowfields
1022
+ * approach meets the shore. Nothing is fished here yet; it is the place first.
1023
+ * World coordinates - z already includes WORLD_NORTH_PAD.
1024
+ */
1025
+ export const FROZEN_LAKE_CENTER = { x: 32, z: 236 };
1026
+ export const FROZEN_LAKE_RX = 14;
1027
+ export const FROZEN_LAKE_RZ = 12;
1028
+ /** The level the ice is frozen at; the lowland around it already lies near it. */
1029
+ export const FROZEN_LAKE_ICE_HEIGHT = 0;
1030
+ /** Holes cut through the ice. The first two have a fisher sitting beside them. */
1031
+ 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 },
1038
+ ];
1039
+ /** Where each fisher sits, and the hole they face. */
1040
+ 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 },
1043
+ ];
1044
+ /** 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 };
1046
+ /**
1047
+ * The bait table on the east bank, between the supply sled and the cooking
1048
+ * fire, and the bait master who works it: he stands at its east end, facing
1049
+ * the table and the ice beyond it.
1050
+ */
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 };
1053
+ /** Signed distance to the ice edge in tiles: at or below zero is on the ice. */
1054
+ export function frozenLakeEdgeDistance(x, z) {
1055
+ const d = Math.hypot((x - FROZEN_LAKE_CENTER.x) / FROZEN_LAKE_RX, (z - FROZEN_LAKE_CENTER.z) / FROZEN_LAKE_RZ);
1056
+ return (d - 1) * Math.min(FROZEN_LAKE_RX, FROZEN_LAKE_RZ);
1057
+ }
1058
+ export function onFrozenLakeIce(x, z) {
1059
+ return frozenLakeEdgeDistance(x, z) <= 0;
1060
+ }
1061
+ /**
1062
+ * The far north-west corner, beyond the lake: the lowland rises to a snow
1063
+ * plateau, the Crystal Gorge cuts across it with a rope bridge over the drop,
1064
+ * and the Frozen Hallway runs north from a rune arch between walls of dark
1065
+ * rock and ice to an iron gate that leads deeper inside (nowhere, yet).
1066
+ * World coordinates - z already includes WORLD_NORTH_PAD.
1067
+ */
1068
+ export const FROZEN_PLATEAU_H = 5;
1069
+ /** Where the lowland starts climbing to the plateau, and where it is level. */
1070
+ export const FROZEN_PLATEAU_RISE_Z = 214;
1071
+ export const FROZEN_PLATEAU_TOP_Z = 198;
1072
+ /** The hills east of here are left as they are. */
1073
+ export const FROZEN_PLATEAU_MAX_X = 46;
1074
+ export const CRYSTAL_GORGE_MIN_Z = 158;
1075
+ export const CRYSTAL_GORGE_MAX_Z = 166;
1076
+ export const CRYSTAL_GORGE_MAX_X = 44;
1077
+ /** The bridge column and the deck's half-width in tiles. */
1078
+ export const CRYSTAL_BRIDGE_X = 21;
1079
+ export const CRYSTAL_BRIDGE_HALF_W = 1;
1080
+ export const FROZEN_HALLWAY_X = 21;
1081
+ export const FROZEN_HALLWAY_HALF_W = 4;
1082
+ export const FROZEN_HALLWAY_WALL_T = 5;
1083
+ export const FROZEN_HALLWAY_WALL_H = 10;
1084
+ /** The rune arch at the mouth and the iron gate at the far end. */
1085
+ export const FROZEN_HALLWAY_MOUTH_Z = 105;
1086
+ export const FROZEN_HALLWAY_GATE_Z = 26;
1087
+ export const FROZEN_HALLWAY_SEAL_Z = 6;
1088
+ /**
1089
+ * The Ice Prison: the rooms behind the hallway's iron gate, carved in the
1090
+ * off-map strip like the other interiors. You arrive in the Frozen Cells,
1091
+ * the prison's gatehouse; a flight of steps at its north end leads down a
1092
+ * short corridor to the Frozen Shrine, where the Warden's door is sealed and
1093
+ * the west and east passages are still choked. World coordinates.
1094
+ */
1095
+ export const ICE_PRISON_CX = UNDERGROUND_ORIGIN_X + 208;
1096
+ export const ICE_PRISON_CELLS = { cx: ICE_PRISON_CX, minZ: 150, maxZ: 172, halfW: 12 };
1097
+ export const ICE_PRISON_CORRIDOR = { cx: ICE_PRISON_CX, minZ: 141, maxZ: 149, halfW: 1 };
1098
+ export const ICE_PRISON_SHRINE = { cx: ICE_PRISON_CX, minZ: 110, maxZ: 140, halfW: 16 };
1099
+ /** Where the gate back out stands (the south wall) and where you arrive. */
1100
+ export const ICE_PRISON_GATE = { x: ICE_PRISON_CX, z: ICE_PRISON_CELLS.maxZ + 1 };
1101
+ export const ICE_PRISON_ENTRY = { x: ICE_PRISON_CX, z: ICE_PRISON_CELLS.maxZ - 2 };
1102
+ /** Gate styles carry the facing letter first; this tag picks the destination. */
1103
+ export const ICE_PRISON_GATE_IN_STYLE = 'n-iceprison';
1104
+ export const ICE_PRISON_GATE_OUT_STYLE = 's-iceprison-out';
1105
+ /** Where the way out drops you: just south of the hallway's gate. */
1106
+ export const ICE_PRISON_OUTSIDE = { x: FROZEN_HALLWAY_X, z: FROZEN_HALLWAY_GATE_Z + 3 };
1107
+ /** The seal on the shrine's platform; rings step up around it. */
1108
+ export const ICE_PRISON_SEAL = { x: ICE_PRISON_CX, z: (ICE_PRISON_SHRINE.minZ + ICE_PRISON_SHRINE.maxZ) / 2 };
1109
+ /**
1110
+ * The frost wolf pack in the Frozen Cells: four tiles across the north and
1111
+ * west of the room, clear of the arrival tile and the way up to the gate, so
1112
+ * a player gets a few steps in before the pack notices. All four stand on
1113
+ * the open floor at height 0 (the drop and its walkway are further east).
1114
+ */
1115
+ export const ICE_PRISON_WOLF_SPAWNS = [
1116
+ { x: ICE_PRISON_CX - 6, z: ICE_PRISON_CELLS.minZ + 6 },
1117
+ { x: ICE_PRISON_CX + 2, z: ICE_PRISON_CELLS.minZ + 4 },
1118
+ { x: ICE_PRISON_CX - 9, z: ICE_PRISON_CELLS.minZ + 13 },
1119
+ { x: ICE_PRISON_CX - 2, z: ICE_PRISON_CELLS.minZ + 10 },
1120
+ ];
1121
+ /** Wall margin included, so lighting and colouring cover the rock around the rooms. */
1122
+ export const ICE_PRISON_BOUNDS = {
1123
+ minX: ICE_PRISON_CX - ICE_PRISON_SHRINE.halfW - 8,
1124
+ maxX: ICE_PRISON_CX + ICE_PRISON_SHRINE.halfW + 8,
1125
+ minZ: ICE_PRISON_SHRINE.minZ - 8,
1126
+ maxZ: ICE_PRISON_CELLS.maxZ + 8,
1127
+ };
1128
+ export function inIcePrison(x, z) {
1129
+ return (x >= ICE_PRISON_BOUNDS.minX && x <= ICE_PRISON_BOUNDS.maxX &&
1130
+ z >= ICE_PRISON_BOUNDS.minZ && z <= ICE_PRISON_BOUNDS.maxZ);
1131
+ }
1132
+ /** How far the hallway's centre line swings either side of FROZEN_HALLWAY_X. */
1133
+ export const FROZEN_HALLWAY_SWING = 7;
1134
+ /** 0 at the gate, 1 at the mouth. */
1135
+ function hallwayT(z) {
1136
+ return (z - FROZEN_HALLWAY_GATE_Z) / (FROZEN_HALLWAY_MOUTH_Z - FROZEN_HALLWAY_GATE_Z);
1137
+ }
1138
+ /**
1139
+ * The hallway is not a straight cut: its centre line swings one full S from
1140
+ * the mouth to the gate, so neither end is in sight of the other, and it
1141
+ * breathes as it goes - two chambers where it widens to thirteen tiles and a
1142
+ * throat between them where it closes to five. Both are functions of z so
1143
+ * the builder, the corridor test and anything dressing the walls agree.
1144
+ */
1145
+ export function frozenHallwayCenterAt(z) {
1146
+ return FROZEN_HALLWAY_X + Math.round(FROZEN_HALLWAY_SWING * Math.sin(2 * Math.PI * hallwayT(z)));
1147
+ }
1148
+ export function frozenHallwayHalfWidthAt(z) {
1149
+ return Math.round(FROZEN_HALLWAY_HALF_W + 2 * Math.sin(3 * Math.PI * hallwayT(z)));
1150
+ }
1151
+ export function inFrozenHallwayCorridor(x, z) {
1152
+ return (z >= FROZEN_HALLWAY_GATE_Z && z <= FROZEN_HALLWAY_MOUTH_Z &&
1153
+ Math.abs(x - frozenHallwayCenterAt(z)) <= frozenHallwayHalfWidthAt(z));
1154
+ }
786
1155
  /** Half-width of the walkable bridge deck (tiles either side of bridge X). */
787
1156
  export const BATTLEFIELD_BRIDGE_HALF_W = 1;
788
1157
  /**
@@ -843,27 +1212,29 @@ export const BATTLEFIELD_GOBLINS_PER_CAMP = 6;
843
1212
  export const BATTLEFIELD_CAMP_CHEST_LIFETIME_MS = 60_000;
844
1213
  /** Soldier camps - closer to the farm (higher world Z), south of the lake. */
845
1214
  export const BATTLEFIELD_SOLDIER_CAMPS = [
846
- { x: 22, z: 720 },
847
- { x: 48, z: 714 },
848
- { x: 74, z: 722 },
849
- { x: 100, z: 712 },
850
- { x: 126, z: 718 },
851
- { x: 152, z: 714 },
852
- { x: 34, z: 710 },
853
- { x: 60, z: 716 },
854
- { x: 86, z: 708 },
855
- { x: 112, z: 714 },
856
- { x: 138, z: 712 },
857
- { x: 164, z: 710 },
858
- { x: 18, z: 716 },
859
- { x: 168, z: 720 },
1215
+ { x: 22, z: bfZ(720) },
1216
+ { x: 48, z: bfZ(714) },
1217
+ { x: 74, z: bfZ(722) },
1218
+ { x: 100, z: bfZ(712) },
1219
+ { x: 126, z: bfZ(718) },
1220
+ { x: 152, z: bfZ(714) },
1221
+ { x: 34, z: bfZ(710) },
1222
+ { x: 60, z: bfZ(716) },
1223
+ { x: 86, z: bfZ(708) },
1224
+ { x: 112, z: bfZ(714) },
1225
+ { x: 138, z: bfZ(712) },
1226
+ { x: 164, z: bfZ(710) },
1227
+ { x: 18, z: bfZ(716) },
1228
+ { x: 168, z: bfZ(720) },
860
1229
  ];
861
1230
  /** The large central soldier camp is commanded by the Battlefield Captain. */
862
- export const BATTLEFIELD_CAPTAIN_CAMP = { x: 119, z: 734 };
1231
+ export const BATTLEFIELD_CAPTAIN_CAMP = { x: 119, z: bfZ(734) };
863
1232
  /**
864
- * Wooden palisade that seals the soldier shore. South run sits one tile north
865
- * of the farm fence (farm min local Z 12 = world 748). Side returns run from
866
- * that line to the muddy lake shore so nobody walks around the ends.
1233
+ * Wooden palisade that seals the soldier shore. The south run used to sit one
1234
+ * tile off the farm's own north fence (farm min local Z 12 = world 748);
1235
+ * BATTLEFIELD_NORTH_SHIFT now leaves open country between the two. Side
1236
+ * returns run from that line to the muddy lake shore so nobody walks around
1237
+ * the ends.
867
1238
  */
868
1239
  export const BATTLEFIELD_FENCE_SOUTH_Z = BATTLEFIELD_MAX_WORLD_Z - 1;
869
1240
  export const BATTLEFIELD_FENCE_WEST_X = BATTLEFIELD_MIN_X;
@@ -888,27 +1259,27 @@ export function isBattlefieldCaptainDoorTile(x, z) {
888
1259
  }
889
1260
  /** Goblin camps - farther wilderness (lower world Z), north of the lake. */
890
1261
  export const BATTLEFIELD_GOBLIN_CAMPS = [
891
- { x: 30, z: 654 },
892
- { x: 58, z: 648 },
893
- { x: 86, z: 658 },
894
- { x: 114, z: 650 },
895
- { x: 142, z: 654 },
896
- { x: 44, z: 650 },
897
- { x: 100, z: 648 },
898
- { x: 160, z: 652 },
899
- { x: 72, z: 650 },
900
- { x: 128, z: 656 },
1262
+ { x: 30, z: bfZ(654) },
1263
+ { x: 58, z: bfZ(648) },
1264
+ { x: 86, z: bfZ(658) },
1265
+ { x: 114, z: bfZ(650) },
1266
+ { x: 142, z: bfZ(654) },
1267
+ { x: 44, z: bfZ(650) },
1268
+ { x: 100, z: bfZ(648) },
1269
+ { x: 160, z: bfZ(652) },
1270
+ { x: 72, z: bfZ(650) },
1271
+ { x: 128, z: bfZ(656) },
901
1272
  ];
902
1273
  /**
903
1274
  * Cultivated cabbage plots on the soldier shore - between lake and camps.
904
1275
  * Filled as neat soil rows (not sparse scatter).
905
1276
  */
906
1277
  export const BATTLEFIELD_CABBAGE_PLOTS = [
907
- { minX: 28, maxX: 38, minZ: 700, maxZ: 706 },
908
- { minX: 52, maxX: 62, minZ: 701, maxZ: 707 },
909
- { minX: 108, maxX: 118, minZ: 701, maxZ: 707 },
910
- { minX: 132, maxX: 142, minZ: 700, maxZ: 706 },
911
- { minX: 154, maxX: 164, minZ: 702, maxZ: 707 },
1278
+ { minX: 28, maxX: 38, minZ: bfZ(700), maxZ: bfZ(706) },
1279
+ { minX: 52, maxX: 62, minZ: bfZ(701), maxZ: bfZ(707) },
1280
+ { minX: 108, maxX: 118, minZ: bfZ(701), maxZ: bfZ(707) },
1281
+ { minX: 132, maxX: 142, minZ: bfZ(700), maxZ: bfZ(706) },
1282
+ { minX: 154, maxX: 164, minZ: bfZ(702), maxZ: bfZ(707) },
912
1283
  ];
913
1284
  export const DISCOVERY_ZONES = [
914
1285
  {
@@ -928,18 +1299,38 @@ export const DISCOVERY_ZONES = [
928
1299
  maxZ: CROSSROADS_LOCAL_Z + 5,
929
1300
  },
930
1301
  {
1302
+ // The id stays 'lil-beach' on purpose. Discovery is persisted per player
1303
+ // as a set of these ids, so renaming it would un-discover the starting
1304
+ // 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.
1306
+ // Only the label is player-facing, and only the label changed.
931
1307
  id: 'lil-beach',
932
- label: 'Lil Beach',
1308
+ label: 'Hope Beach',
933
1309
  minX: BEACH_GATE_X - 48,
934
1310
  maxX: BEACH_GATE_X + 48,
935
1311
  minZ: 56,
936
- maxZ: 95,
1312
+ // Out along the Long Pier as far as its second smashed span. Past that is
1313
+ // its own zone - see 'long-pier'.
1314
+ maxZ: toLocalZ(HOPE_PIER_GAP2_MAX_Z),
1315
+ },
1316
+ {
1317
+ /*
1318
+ * The seaward half of the pier, which cannot be reached until both spans
1319
+ * are planked. Its own zone rather than more Hope Beach, so getting out
1320
+ * there pays the discovery it deserves.
1321
+ */
1322
+ id: 'long-pier',
1323
+ label: 'The Long Pier',
1324
+ minX: HOPE_PIER_X - 4,
1325
+ maxX: HOPE_PIER_X + 4,
1326
+ minZ: toLocalZ(HOPE_PIER_GAP2_MAX_Z + 1),
1327
+ maxZ: toLocalZ(HOPE_PIER_END_Z),
937
1328
  },
938
1329
  {
939
1330
  id: 'henson-pond',
940
1331
  label: 'Henson Pond',
941
- minX: 278,
942
- maxX: 320,
1332
+ minX: 294,
1333
+ maxX: 336,
943
1334
  minZ: 24,
944
1335
  maxZ: 50,
945
1336
  },
@@ -996,6 +1387,16 @@ export const DISCOVERY_ZONES = [
996
1387
  minZ: SPIDER_CAVE_MOUTH_Z - 6 - WORLD_NORTH_PAD,
997
1388
  maxZ: SPIDER_CAVE_MOUTH_Z + 6 - WORLD_NORTH_PAD,
998
1389
  },
1390
+ {
1391
+ // Bounds come from the zone descriptor so the card fires exactly at the
1392
+ // silk, wherever the hollow is moved to.
1393
+ id: 'spider-hollow',
1394
+ label: dreadwoodsNpcZone('spider-hollow').label,
1395
+ minX: dreadwoodsNpcZone('spider-hollow').cx - dreadwoodsNpcZone('spider-hollow').rx,
1396
+ maxX: dreadwoodsNpcZone('spider-hollow').cx + dreadwoodsNpcZone('spider-hollow').rx,
1397
+ minZ: dreadwoodsNpcZone('spider-hollow').cz - dreadwoodsNpcZone('spider-hollow').rz - WORLD_NORTH_PAD,
1398
+ maxZ: dreadwoodsNpcZone('spider-hollow').cz + dreadwoodsNpcZone('spider-hollow').rz - WORLD_NORTH_PAD,
1399
+ },
999
1400
  {
1000
1401
  id: 'trout-fishery',
1001
1402
  label: 'Trout Fishery',
@@ -1004,6 +1405,64 @@ export const DISCOVERY_ZONES = [
1004
1405
  minZ: FISHERY_NORTH_Z - 3 - WORLD_NORTH_PAD,
1005
1406
  maxZ: FISHERY_SOUTH_Z + 5 - WORLD_NORTH_PAD,
1006
1407
  },
1408
+ // The snow. Until 2026-09-09 nothing north of the goblin camps was a place:
1409
+ // a hundred thousand walkable tiles that paid no Explorer XP and drew no
1410
+ // label on the map. discoveryZoneAt takes the first match, so the two small
1411
+ // zones come before the field that surrounds them. Z is playable-local,
1412
+ // hence the pad subtraction (see Calverton above).
1413
+ {
1414
+ id: 'northern-gate',
1415
+ label: 'Northern Gate',
1416
+ minX: NORTHERN_GARRISON_MIN_X - 2,
1417
+ maxX: NORTHERN_GARRISON_MAX_X + 2,
1418
+ minZ: NORTHERN_GARRISON_MIN_Z - 2 - WORLD_NORTH_PAD,
1419
+ maxZ: NORTHERN_WALL_Z + 22 - WORLD_NORTH_PAD,
1420
+ },
1421
+ {
1422
+ // The west edge of the snow, where the boats and jetties are iced in.
1423
+ id: 'frozen-shore',
1424
+ label: 'The Frozen Shore',
1425
+ minX: 0,
1426
+ maxX: 12,
1427
+ minZ: 0 - WORLD_NORTH_PAD,
1428
+ maxZ: 345 - WORLD_NORTH_PAD,
1429
+ },
1430
+ {
1431
+ // The frozen lake and the fishers' ground on its banks. Listed before the
1432
+ // snowfields, which cover the whole north and would otherwise claim it.
1433
+ id: 'frozen-lake',
1434
+ label: 'The Frozen Lake',
1435
+ minX: FROZEN_LAKE_CENTER.x - FROZEN_LAKE_RX - 4,
1436
+ maxX: FROZEN_LAKE_CENTER.x + FROZEN_LAKE_RX + 10,
1437
+ minZ: FROZEN_LAKE_CENTER.z - FROZEN_LAKE_RZ - 8 - WORLD_NORTH_PAD,
1438
+ maxZ: FROZEN_LAKE_CENTER.z + FROZEN_LAKE_RZ + 10 - WORLD_NORTH_PAD,
1439
+ },
1440
+ {
1441
+ // The plateau either side of the gorge, bridge included.
1442
+ id: 'crystal-gorge',
1443
+ label: 'The Crystal Gorge',
1444
+ minX: 3,
1445
+ maxX: FROZEN_PLATEAU_MAX_X + 2,
1446
+ minZ: FROZEN_HALLWAY_MOUTH_Z + 8 - WORLD_NORTH_PAD,
1447
+ maxZ: FROZEN_PLATEAU_RISE_Z - 2 - WORLD_NORTH_PAD,
1448
+ },
1449
+ {
1450
+ // The walled canyon from the rune arch to the iron gate.
1451
+ id: 'frozen-hallway',
1452
+ label: 'The Frozen Hallway',
1453
+ minX: FROZEN_HALLWAY_X - FROZEN_HALLWAY_SWING - FROZEN_HALLWAY_HALF_W - FROZEN_HALLWAY_WALL_T - 4,
1454
+ maxX: FROZEN_HALLWAY_X + FROZEN_HALLWAY_SWING + FROZEN_HALLWAY_HALF_W + FROZEN_HALLWAY_WALL_T + 4,
1455
+ minZ: FROZEN_HALLWAY_SEAL_Z - 2 - WORLD_NORTH_PAD,
1456
+ maxZ: FROZEN_HALLWAY_MOUTH_Z + 7 - WORLD_NORTH_PAD,
1457
+ },
1458
+ {
1459
+ id: 'northern-snowfields',
1460
+ label: 'The Snowfields',
1461
+ minX: 0,
1462
+ maxX: NORTHERN_WALL_MAX_X,
1463
+ minZ: 0 - WORLD_NORTH_PAD,
1464
+ maxZ: NORTHERN_WALL_Z - 25 - WORLD_NORTH_PAD,
1465
+ },
1007
1466
  ];
1008
1467
  export function discoveryZoneAt(worldX, worldZ) {
1009
1468
  for (const zone of DISCOVERY_ZONES) {
@@ -1039,15 +1498,21 @@ export function inMineYard(x, z, margin = 0) {
1039
1498
  */
1040
1499
  export const WORLD_MAP_LANDMARKS = [
1041
1500
  { x: NORTHERN_GATE_X, z: NORTHERN_WALL_Z, label: 'Northern Gate' },
1501
+ { x: NORTHERN_CAMP_CENTER.x, z: NORTHERN_CAMP_CENTER.z, label: 'War Camp', labelSide: 'right' },
1502
+ { x: 8, z: 200, label: 'Frozen Shore', labelSide: 'right' },
1503
+ { x: FROZEN_LAKE_CENTER.x, z: FROZEN_LAKE_CENTER.z, label: 'Frozen Lake', labelSide: 'right' },
1504
+ { x: CRYSTAL_BRIDGE_X, z: (CRYSTAL_GORGE_MIN_Z + CRYSTAL_GORGE_MAX_Z) / 2, label: 'Crystal Bridge', labelSide: 'right' },
1505
+ { x: FROZEN_HALLWAY_X, z: (FROZEN_HALLWAY_GATE_Z + FROZEN_HALLWAY_MOUTH_Z) / 2, label: 'Frozen Hallway', labelSide: 'right' },
1042
1506
  { ...discoveryZoneCenter('no-harm-farm'), label: 'Farm' },
1043
1507
  { x: CROSSROADS_WORLD_X, z: toWorldZ(CROSSROADS_LOCAL_Z), label: 'Crossroads' },
1044
1508
  { ...discoveryZoneCenter('henson-pond'), label: 'Pond' },
1045
1509
  {
1046
1510
  x: BEACH_GATE_X,
1047
1511
  z: Math.round((BEACH_SAND_START_Z + OCEAN_START_Z) / 2),
1048
- label: 'Lil Beach',
1512
+ label: 'Hope Beach',
1049
1513
  labelSide: 'on',
1050
1514
  },
1515
+ { x: HOPE_PIER_X, z: HOPE_PIER_END_Z, label: 'The Long Pier', labelSide: 'left' },
1051
1516
  {
1052
1517
  x: Math.round((FISHERY_WATER_MIN_X + FISHERY_WATER_MAX_X) / 2),
1053
1518
  z: Math.round((FISHERY_WATER_NORTH_Z + FISHERY_WATER_SOUTH_Z) / 2),
@@ -1142,5 +1607,8 @@ export const MINE_SEAMS = [
1142
1607
  { dx: -5, dz: -7, style: 'copper' },
1143
1608
  // The single tin seam works the eastern side of the face.
1144
1609
  { dx: 7, dz: -5, style: 'tin' },
1610
+ // Iron seams along the deep northern and eastern faces.
1611
+ { dx: 5, dz: -7, style: 'iron' },
1612
+ { dx: 8, dz: -1, style: 'iron' },
1145
1613
  ];
1146
1614
  //# sourceMappingURL=landmarks.js.map