mudlet-map-renderer 2.5.1 → 2.6.1

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 (37) hide show
  1. package/README.md +68 -0
  2. package/dist/Area-MLM4Xe0E.js +157 -0
  3. package/dist/Area-MLM4Xe0E.js.map +1 -0
  4. package/dist/MapReader-BeVNpm6y.js +77 -0
  5. package/dist/MapReader-BeVNpm6y.js.map +1 -0
  6. package/dist/SkeletonMapReader-ZPwsqemZ.js +351 -0
  7. package/dist/SkeletonMapReader-ZPwsqemZ.js.map +1 -0
  8. package/dist/bigmap/PlaneIndex.d.ts +42 -0
  9. package/dist/bigmap/Skeleton.d.ts +60 -0
  10. package/dist/bigmap/SkeletonMapReader.d.ts +65 -0
  11. package/dist/bigmap/buildSkeleton.d.ts +23 -0
  12. package/dist/bigmap/index.d.ts +24 -0
  13. package/dist/bigmap.mjs +61 -0
  14. package/dist/bigmap.mjs.map +1 -0
  15. package/dist/binary/convert.d.ts +19 -0
  16. package/dist/binary/index.d.ts +2 -0
  17. package/dist/binary/loadMudletMap.d.ts +62 -0
  18. package/dist/binary.mjs +195 -8
  19. package/dist/binary.mjs.map +1 -1
  20. package/dist/export/exportViewport.d.ts +25 -0
  21. package/dist/index.d.ts +7 -1
  22. package/dist/index.mjs +978 -601
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/reader/Area.d.ts +8 -2
  25. package/dist/reader/HashLookup.d.ts +17 -0
  26. package/dist/reader/ViewportDataSource.d.ts +33 -0
  27. package/dist/render/CullIndex.d.ts +54 -0
  28. package/dist/rendering/KonvaRenderBackend.d.ts +68 -0
  29. package/dist/rendering/SceneManager.d.ts +28 -5
  30. package/dist/rendering/lod/LodController.d.ts +45 -0
  31. package/dist/rendering/lod/RasterOverview.d.ts +46 -0
  32. package/dist/rendering/lod/lodDecision.d.ts +45 -0
  33. package/dist/rendering/lod/roomsOnlyArea.d.ts +16 -0
  34. package/dist/types/Settings.d.ts +63 -0
  35. package/package.json +9 -4
  36. package/dist/MapReader-Brzg-X7T.js +0 -224
  37. package/dist/MapReader-Brzg-X7T.js.map +0 -1
package/dist/binary.mjs CHANGED
@@ -1,13 +1,83 @@
1
- import { t as e } from "./MapReader-Brzg-X7T.js";
2
- import { readMapFromBuffer as t, readerExport as n } from "mudlet-map-binary-reader";
1
+ import { t as e } from "./MapReader-BeVNpm6y.js";
2
+ import { t } from "./SkeletonMapReader-ZPwsqemZ.js";
3
+ import { convertLabel as n, convertRoom as r, readMapFromBuffer as i, readerExport as a, streamRooms as o } from "mudlet-map-binary-reader";
4
+ //#region src/binary/convert.ts
5
+ function s(e, t, n = "") {
6
+ let r = {};
7
+ for (let t in e.customLines) {
8
+ let n = e.customLines[t];
9
+ r[t] = {
10
+ points: n.points,
11
+ attributes: {
12
+ color: {
13
+ alpha: 255,
14
+ r: n.attributes.color.r,
15
+ g: n.attributes.color.g,
16
+ b: n.attributes.color.b
17
+ },
18
+ style: n.attributes.style,
19
+ arrow: n.attributes.arrow
20
+ }
21
+ };
22
+ }
23
+ return {
24
+ id: e.id,
25
+ area: e.area,
26
+ x: e.x,
27
+ y: e.y,
28
+ z: e.z,
29
+ areaId: t,
30
+ weight: e.weight,
31
+ roomChar: e.roomChar ?? "",
32
+ name: e.name,
33
+ userData: e.userData,
34
+ customLines: r,
35
+ stubs: e.stubs,
36
+ hash: e.hash ?? n,
37
+ env: e.env ?? 0,
38
+ exits: e.exits,
39
+ doors: e.doors,
40
+ specialExits: e.specialExits,
41
+ exitLocks: e.exitLocks,
42
+ exitWeights: e.exitWeights,
43
+ mSpecialExitLocks: e.mSpecialExitLocks
44
+ };
45
+ }
46
+ function c(e, t) {
47
+ return {
48
+ labelId: e.labelId ?? e.id,
49
+ areaId: e.areaId ?? t,
50
+ pixMap: e.pixMap || void 0,
51
+ X: e.X,
52
+ Y: e.Y,
53
+ Z: e.Z,
54
+ Width: e.Width,
55
+ Height: e.Height,
56
+ Text: e.Text,
57
+ FgColor: e.FgColor,
58
+ BgColor: e.BgColor,
59
+ noScaling: e.noScaling,
60
+ showOnTop: e.showOnTop
61
+ };
62
+ }
63
+ var l = (e) => !!e && Object.keys(e).length > 0;
64
+ function u(e) {
65
+ return !!e.symbol || l(e.customLines) || l(e.mSpecialExits) || (e.stubs?.length ?? 0) > 0 || l(e.doors) || (e.exitLocks?.length ?? 0) > 0 || e.weight !== 1 || l(e.exitWeights);
66
+ }
67
+ //#endregion
3
68
  //#region src/binary/BinaryMapReader.ts
4
- var r = class r {
69
+ var d = class t {
5
70
  constructor(t) {
6
- let { mapData: r, colors: i } = n(t);
7
- this.reader = new e(r, i);
71
+ let { mapData: n, colors: r } = a(t);
72
+ this.reader = new e(n.map((e) => ({
73
+ areaName: e.areaName,
74
+ areaId: e.areaId,
75
+ rooms: e.rooms.map((t) => s(t, e.areaId, t.hash)),
76
+ labels: e.labels.map((t) => c(t, parseInt(e.areaId)))
77
+ })), r);
8
78
  }
9
79
  static fromBuffer(e) {
10
- return new r(t(e));
80
+ return new t(i(e));
11
81
  }
12
82
  getArea(e) {
13
83
  return this.reader.getArea(e);
@@ -27,8 +97,125 @@ var r = class r {
27
97
  getSymbolColor(e, t) {
28
98
  return this.reader.getSymbolColor(e, t);
29
99
  }
30
- };
100
+ }, f = [
101
+ "north",
102
+ "northeast",
103
+ "east",
104
+ "southeast",
105
+ "south",
106
+ "southwest",
107
+ "west",
108
+ "northwest",
109
+ "up",
110
+ "down",
111
+ "in",
112
+ "out"
113
+ ], p = class extends Error {};
114
+ function m(e) {
115
+ let t;
116
+ try {
117
+ o(e, () => {
118
+ throw new p();
119
+ }, (e) => {
120
+ let n = 0;
121
+ for (let t in e.areas ?? {}) n += e.areas[t].rooms.length;
122
+ throw t = {
123
+ header: e,
124
+ total: n
125
+ }, new p();
126
+ });
127
+ } catch (e) {
128
+ if (!(e instanceof p)) throw e;
129
+ }
130
+ if (!t) throw Error("failed to decode map header");
131
+ return t;
132
+ }
133
+ function h(e) {
134
+ let t = {};
135
+ for (let n in e.areas ?? {}) t[n] = e.areas[n].gridMode;
136
+ return t;
137
+ }
138
+ var g = (e) => !!e && Object.keys(e).length > 0;
139
+ function _(e) {
140
+ let { mapData: t, colors: n } = a(i(e));
141
+ return {
142
+ kind: "plain",
143
+ map: t.map((e) => ({
144
+ areaName: e.areaName,
145
+ areaId: e.areaId,
146
+ rooms: e.rooms.map((t) => s(t, e.areaId, t.hash)),
147
+ labels: e.labels.map((t) => c(t, parseInt(e.areaId)))
148
+ })),
149
+ envs: n
150
+ };
151
+ }
152
+ function v(e, t, i, a, l = u) {
153
+ let d = new Int32Array(i), p = new Int32Array(i), m = new Int32Array(i), _ = new Int32Array(i), v = new Int32Array(i), y = new Int32Array(i), b = new Int32Array(i * 12).fill(-1), x = Array(i), S = [], C = [], w = {};
154
+ for (let e in t.mpRoomDbHashToRoomId) w[t.mpRoomDbHashToRoomId[e]] = e;
155
+ let T = () => typeof performance < "u" ? performance.now() : Date.now(), E = T(), D = 0;
156
+ o(e, (e, t) => {
157
+ d[D] = t.x, p[D] = t.y, m[D] = t.z, _[D] = t.area, v[D] = t.environment, y[D] = e;
158
+ let n = D * 12;
159
+ for (let e = 0; e < 12; e++) b[n + e] = t[f[e]];
160
+ if (x[D] = t.name ?? "", g(t.userData) && S.push({
161
+ id: e,
162
+ data: t.userData
163
+ }), l(t)) {
164
+ let n = r(e, t, w[e]);
165
+ C.push(s(n, String(t.area), w[e]));
166
+ }
167
+ if (D++, a) {
168
+ let e = T();
169
+ e - E >= 80 && (E = e, a(D, i));
170
+ }
171
+ }), a?.(D, i);
172
+ let O = {};
173
+ for (let e in t.mCustomEnvColors ?? {}) {
174
+ let n = t.mCustomEnvColors[e];
175
+ O[e] = {
176
+ r: n.r,
177
+ g: n.g,
178
+ b: n.b
179
+ };
180
+ }
181
+ let k = [];
182
+ for (let e in t.labels ?? {}) {
183
+ let r = Number(e);
184
+ for (let e of t.labels[r]) k.push(c(n(e), r));
185
+ }
186
+ return {
187
+ kind: "skeleton",
188
+ skeleton: {
189
+ count: D,
190
+ x: d,
191
+ y: p,
192
+ z: m,
193
+ area: _,
194
+ env: v,
195
+ id: y,
196
+ exits: b,
197
+ areaNames: t.areaNames ?? {},
198
+ areaGridMode: h(t),
199
+ customEnvColors: O,
200
+ names: x,
201
+ userData: S,
202
+ detailRooms: C,
203
+ labels: k,
204
+ hashToId: t.mpRoomDbHashToRoomId ?? {}
205
+ }
206
+ };
207
+ }
208
+ function y(e, t = {}) {
209
+ let { mode: n = "auto", threshold: r = 5e4, onProgress: i, isDetailRoom: a } = t, { header: o, total: s } = m(e);
210
+ return (n === "auto" ? s > r ? "streaming" : "plain" : n) === "plain" ? _(e) : v(e, o, s, i, a);
211
+ }
212
+ function b(n) {
213
+ return n.kind === "plain" ? new e(n.map, n.envs) : new t(n.skeleton);
214
+ }
215
+ function x(e, t) {
216
+ return b(y(e, t));
217
+ }
31
218
  //#endregion
32
- export { r as BinaryMapReader };
219
+ export { d as BinaryMapReader, x as loadMudletMap, y as parseMudletMap, b as readerFromLoadedMap };
33
220
 
34
221
  //# sourceMappingURL=binary.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"binary.mjs","names":[],"sources":["../src/binary/BinaryMapReader.ts"],"sourcesContent":["import {\n type MudletMap,\n readerExport,\n readMapFromBuffer,\n} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\n\n/**\n * {@link IMapReader} implementation backed by a parsed Mudlet binary map.\n *\n * Convenience wrapper around the existing {@link MapReader} that converts a\n * {@link MudletMap} (the in-memory binary model from\n * `mudlet-map-binary-reader`) into the renderer's `{mapData, colors}` shape\n * via `readerExport()`, then forwards every `IMapReader` call to the wrapped\n * reader.\n *\n * Useful for downstream apps (the upstream Mudix client is one) that already\n * own binary `.dat` bytes and want a single object they can hand to\n * {@link MapRenderer} without staging a separate JSON export step.\n *\n * `mudlet-map-binary-reader` is a `peerDependency`; consumers who do not use\n * binary maps never pay the bundle cost.\n */\nexport default class BinaryMapReader implements IMapReader {\n private readonly reader: MapReader;\n\n /** Build directly from a parsed {@link MudletMap}. */\n constructor(model: MudletMap) {\n const {mapData, colors} = readerExport(model);\n // RendererRoom (binary-reader output) and MapData.Room (renderer\n // input) overlap structurally — RendererRoom is missing only\n // `areaId`, which the renderer never reads from rooms (only from\n // the area). Cast through `unknown` to bridge the nominal gap.\n this.reader = new MapReader(mapData as unknown as MapData.Map, colors);\n }\n\n /**\n * Parse the given binary map buffer and construct a reader from it.\n * Accepts any `Uint8Array`: in Node pass `fs.readFileSync(path)`; in the\n * browser pass `new Uint8Array(await file.arrayBuffer())`. The underlying\n * `mudlet-map-binary-reader` (>=1.0.0) is browser-safe and needs no\n * Node `Buffer` polyfill.\n */\n static fromBuffer(buf: Parameters<typeof readMapFromBuffer>[0]): BinaryMapReader {\n return new BinaryMapReader(readMapFromBuffer(buf));\n }\n\n // --- IMapReader forwarding ---\n\n getArea(areaId: number): IArea {\n return this.reader.getArea(areaId);\n }\n\n getAreas(): IArea[] {\n return this.reader.getAreas();\n }\n\n getRooms(): MapData.Room[] {\n return this.reader.getRooms();\n }\n\n getRoom(roomId: number): MapData.Room {\n return this.reader.getRoom(roomId);\n }\n\n getColorValue(envId: number): string {\n return this.reader.getColorValue(envId);\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n return this.reader.getSymbolColor(envId, opacity);\n }\n}\n"],"mappings":";;;AAwBA,IAAqB,IAArB,MAAqB,EAAsC;CAIvD,YAAY,GAAkB;EAC1B,IAAM,EAAC,YAAS,cAAU,EAAa,EAAM;AAK7C,OAAK,SAAS,IAAI,EAAU,GAAmC,EAAO;;CAU1E,OAAO,WAAW,GAA+D;AAC7E,SAAO,IAAI,EAAgB,EAAkB,EAAI,CAAC;;CAKtD,QAAQ,GAAuB;AAC3B,SAAO,KAAK,OAAO,QAAQ,EAAO;;CAGtC,WAAoB;AAChB,SAAO,KAAK,OAAO,UAAU;;CAGjC,WAA2B;AACvB,SAAO,KAAK,OAAO,UAAU;;CAGjC,QAAQ,GAA8B;AAClC,SAAO,KAAK,OAAO,QAAQ,EAAO;;CAGtC,cAAc,GAAuB;AACjC,SAAO,KAAK,OAAO,cAAc,EAAM;;CAG3C,eAAe,GAAe,GAA0B;AACpD,SAAO,KAAK,OAAO,eAAe,GAAO,EAAQ"}
1
+ {"version":3,"file":"binary.mjs","names":[],"sources":["../src/binary/convert.ts","../src/binary/BinaryMapReader.ts","../src/binary/loadMudletMap.ts"],"sourcesContent":["import type {RendererRoom, RendererLabel, MudletRoom} from \"mudlet-map-binary-reader\";\n\n/**\n * RendererRoom -> MapData.Room: fills the fields RendererRoom leaves optional\n * (`env`/`roomChar` are only set upstream when truthy; `hash` is resolved\n * separately from the header's `mpRoomDbHashToRoomId` index since streamRooms\n * doesn't hydrate it per-room). `customLines` colour drops `alpha` upstream —\n * SpecialExitStyle only ever reads r/g/b, so 255 (opaque) is a safe filler,\n * never a real behavioural difference.\n */\nexport function toMapRoom(r: RendererRoom, areaId: string, hash = \"\"): MapData.Room {\n const customLines: Record<string, MapData.Line> = {};\n for (const key in r.customLines) {\n const line = r.customLines[key];\n customLines[key] = {\n points: line.points,\n attributes: {\n color: {alpha: 255, r: line.attributes.color.r, g: line.attributes.color.g, b: line.attributes.color.b},\n style: line.attributes.style,\n arrow: line.attributes.arrow,\n },\n };\n }\n return {\n id: r.id,\n area: r.area,\n x: r.x,\n y: r.y,\n z: r.z,\n areaId,\n weight: r.weight,\n roomChar: r.roomChar ?? \"\",\n name: r.name,\n userData: r.userData,\n customLines,\n stubs: r.stubs,\n hash: r.hash ?? hash,\n env: r.env ?? 0,\n exits: r.exits as Record<MapData.direction, number>,\n // Mudlet door states are always 1|2|3 at runtime; MudletRoom types them as plain number.\n doors: r.doors as Record<string, 1 | 2 | 3>,\n specialExits: r.specialExits,\n exitLocks: r.exitLocks,\n exitWeights: r.exitWeights,\n mSpecialExitLocks: r.mSpecialExitLocks,\n };\n}\n\n/** RendererLabel -> MapData.Label; areaId/labelId are stamped from the enclosing header.labels[areaId] key when omitted. */\nexport function toMapLabel(l: RendererLabel, fallbackAreaId: number): MapData.Label {\n return {\n labelId: l.labelId ?? l.id,\n areaId: l.areaId ?? fallbackAreaId,\n pixMap: l.pixMap || undefined,\n X: l.X, Y: l.Y, Z: l.Z, Width: l.Width, Height: l.Height, Text: l.Text,\n FgColor: l.FgColor, BgColor: l.BgColor,\n noScaling: l.noScaling, showOnTop: l.showOnTop,\n };\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\n/**\n * A room carries non-default metadata the skeleton's compact columns can't\n * encode (or that a plain visual-detail check would silently drop —\n * `weight`, `exitWeights`) and must be promoted to a fully materialised\n * MapData.Room.\n */\nexport function hasExtraDetail(room: MudletRoom): boolean {\n return !!room.symbol || hasKeys(room.customLines) || hasKeys(room.mSpecialExits) ||\n (room.stubs?.length ?? 0) > 0 || hasKeys(room.doors) || (room.exitLocks?.length ?? 0) > 0 ||\n room.weight !== 1 || hasKeys(room.exitWeights);\n}\n","import {\n type MudletMap,\n readerExport,\n readMapFromBuffer,\n} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport type {IArea} from \"../reader/Area\";\nimport {toMapRoom, toMapLabel} from \"./convert\";\n\n/**\n * {@link IMapReader} implementation backed by a parsed Mudlet binary map.\n *\n * Convenience wrapper around the existing {@link MapReader} that converts a\n * {@link MudletMap} (the in-memory binary model from\n * `mudlet-map-binary-reader`) into the renderer's `{mapData, colors}` shape\n * via `readerExport()`, then forwards every `IMapReader` call to the wrapped\n * reader.\n *\n * Useful for downstream apps (the upstream Mudix client is one) that already\n * own binary `.dat` bytes and want a single object they can hand to\n * {@link MapRenderer} without staging a separate JSON export step.\n *\n * `mudlet-map-binary-reader` is a `peerDependency`; consumers who do not use\n * binary maps never pay the bundle cost.\n */\nexport default class BinaryMapReader implements IMapReader {\n private readonly reader: MapReader;\n\n /** Build directly from a parsed {@link MudletMap}. */\n constructor(model: MudletMap) {\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n this.reader = new MapReader(map, colors);\n }\n\n /**\n * Parse the given binary map buffer and construct a reader from it.\n * Accepts any `Uint8Array`: in Node pass `fs.readFileSync(path)`; in the\n * browser pass `new Uint8Array(await file.arrayBuffer())`. The underlying\n * `mudlet-map-binary-reader` (>=1.0.0) is browser-safe and needs no\n * Node `Buffer` polyfill.\n */\n static fromBuffer(buf: Parameters<typeof readMapFromBuffer>[0]): BinaryMapReader {\n return new BinaryMapReader(readMapFromBuffer(buf));\n }\n\n // --- IMapReader forwarding ---\n\n getArea(areaId: number): IArea {\n return this.reader.getArea(areaId);\n }\n\n getAreas(): IArea[] {\n return this.reader.getAreas();\n }\n\n getRooms(): MapData.Room[] {\n return this.reader.getRooms();\n }\n\n getRoom(roomId: number): MapData.Room {\n return this.reader.getRoom(roomId);\n }\n\n getColorValue(envId: number): string {\n return this.reader.getColorValue(envId);\n }\n\n getSymbolColor(envId: number, opacity?: number): string {\n return this.reader.getSymbolColor(envId, opacity);\n }\n}\n","import {streamRooms, convertRoom, convertLabel, readMapFromBuffer, readerExport} from \"mudlet-map-binary-reader\";\nimport type {MudletMapHeader, MudletRoom} from \"mudlet-map-binary-reader\";\nimport MapReader, {type IMapReader} from \"../reader/MapReader\";\nimport SkeletonMapReader from \"../bigmap/SkeletonMapReader\";\nimport type {MapSkeleton} from \"../bigmap/Skeleton\";\nimport {toMapRoom, toMapLabel, hasExtraDetail} from \"./convert\";\n\n// Cardinal exit fields on MudletRoom, in the order packed into the skeleton\n// (12 slots per room; -1 = no exit).\nconst DIRS = [\n \"north\", \"northeast\", \"east\", \"southeast\", \"south\", \"southwest\",\n \"west\", \"northwest\", \"up\", \"down\", \"in\", \"out\",\n] as const;\n\nexport type LoadMode = \"auto\" | \"plain\" | \"streaming\";\n\nexport interface LoadMudletMapOptions {\n /** 'auto' (default) picks 'plain' or 'streaming' from the header's total room count vs. {@link threshold}. */\n mode?: LoadMode;\n /**\n * Total-room-count cutoff for 'auto' (default 50,000). Below it: a full\n * parse (every field, no skeleton overhead — a `MapSkeleton` has no\n * upside for a map small enough to hold as an object graph). Above it:\n * stream straight into a `MapSkeleton` — the full parsed map and the\n * skeleton are never both resident in memory at once.\n */\n threshold?: number;\n /** Invoked periodically (every 200,000 rooms) — the streaming path only; the full parse has no per-room hook. */\n onProgress?: (roomsRead: number, total: number) => void;\n /**\n * Streaming path only (the full parse always materialises every field —\n * there's no skeleton to promote into). Overrides which rooms are\n * promoted to a fully materialised `MapData.Room` in `MapSkeleton.detailRooms`;\n * default `hasExtraDetail` (doors/customLines/specialExits/stubs/exitLocks/\n * non-default weight/exitWeights — fields the compact columns can't encode\n * at all). Pass `() => true` to promote every room — correct at any size,\n * but memory-equivalent to a full `MapReader` object graph for the room\n * data itself, so it only makes sense when the map's total room count\n * doesn't actually need the streaming path's memory savings.\n */\n isDetailRoom?: (room: MudletRoom) => boolean;\n}\n\n/**\n * Portable, structured-clone/transfer-safe parse result — no live reader yet\n * (see {@link readerFromLoadedMap}). Splitting parse from materialisation\n * lets a Web Worker run the (potentially expensive) parse and hand back only\n * the result; the skeleton kind's typed arrays should be passed as\n * transferables (`[skeleton.x.buffer, skeleton.y.buffer, ...]`).\n */\nexport type LoadedMudletMap =\n | {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]}\n | {kind: \"skeleton\"; skeleton: MapSkeleton};\n\nclass HeaderAbort extends Error {}\n\n/** Reads only the header (areas/labels/colours), aborting before the — potentially huge — rooms blob. */\nfunction peekHeader(bytes: Uint8Array): {header: MudletMapHeader; total: number} {\n let result: {header: MudletMapHeader; total: number} | undefined;\n try {\n streamRooms(bytes, () => {\n throw new HeaderAbort();\n }, (hdr) => {\n let total = 0;\n for (const k in hdr.areas ?? {}) total += hdr.areas[k as unknown as number].rooms.length;\n result = {header: hdr, total};\n throw new HeaderAbort();\n });\n } catch (e) {\n if (!(e instanceof HeaderAbort)) throw e;\n }\n if (!result) throw new Error(\"failed to decode map header\");\n return result;\n}\n\nfunction buildAreaGridMode(header: MudletMapHeader): Record<number, boolean> {\n const areaGridMode: Record<number, boolean> = {};\n for (const k in header.areas ?? {}) {\n areaGridMode[k as unknown as number] = header.areas[k as unknown as number].gridMode;\n }\n return areaGridMode;\n}\n\nconst hasKeys = (o: Record<string, unknown> | undefined | null): boolean => !!o && Object.keys(o).length > 0;\n\nfunction parsePlain(bytes: Uint8Array): {kind: \"plain\"; map: MapData.Map; envs: MapData.Env[]} {\n const model = readMapFromBuffer(bytes);\n const {mapData, colors} = readerExport(model);\n const map: MapData.Map = mapData.map(a => ({\n areaName: a.areaName,\n areaId: a.areaId,\n // readerExport already resolves each room's hash — pass-through, no reverse lookup needed here.\n rooms: a.rooms.map(r => toMapRoom(r, a.areaId, r.hash)),\n labels: a.labels.map(l => toMapLabel(l, parseInt(a.areaId))),\n }));\n return {kind: \"plain\", map, envs: colors};\n}\n\nfunction parseStreaming(\n bytes: Uint8Array, header: MudletMapHeader, total: number,\n onProgress?: (roomsRead: number, total: number) => void,\n isDetailRoom: (room: MudletRoom) => boolean = hasExtraDetail,\n): {kind: \"skeleton\"; skeleton: MapSkeleton} {\n const x = new Int32Array(total), y = new Int32Array(total), z = new Int32Array(total);\n const area = new Int32Array(total), env = new Int32Array(total), id = new Int32Array(total);\n const exits = new Int32Array(total * 12).fill(-1);\n const names: string[] = new Array(total);\n const userData: {id: number; data: Record<string, string>}[] = [];\n const detailRooms: MapData.Room[] = [];\n\n const roomIdToHash: Record<number, string> = {};\n for (const hash in header.mpRoomDbHashToRoomId) {\n roomIdToHash[header.mpRoomDbHashToRoomId[hash]] = hash;\n }\n\n // Time-based throttle (not every-N-rooms): gives smooth, frequent updates\n // regardless of map size or per-room cost, instead of a handful of big\n // jumps on a huge map or none at all on a small one.\n const now = () => (typeof performance !== \"undefined\" ? performance.now() : Date.now());\n let lastReport = now();\n let i = 0;\n streamRooms(bytes, (roomId, room) => {\n x[i] = room.x;\n y[i] = room.y; // RAW map space — SkeletonMapReader converts to renderer space\n z[i] = room.z;\n area[i] = room.area;\n env[i] = room.environment;\n id[i] = roomId;\n const base = i * 12;\n for (let d = 0; d < 12; d++) exits[base + d] = room[DIRS[d]] as number;\n\n // Name + userData for EVERY room (compact columns), so any room — drawn\n // or looked up by id — is complete, without promoting it to a heavy object.\n names[i] = room.name ?? \"\";\n if (hasKeys(room.userData)) userData.push({id: roomId, data: room.userData});\n\n if (isDetailRoom(room)) {\n const rr = convertRoom(roomId, room, roomIdToHash[roomId]);\n detailRooms.push(toMapRoom(rr, String(room.area), roomIdToHash[roomId]));\n }\n i++;\n if (onProgress) {\n const t = now();\n if (t - lastReport >= 80) {\n lastReport = t;\n onProgress(i, total);\n }\n }\n });\n onProgress?.(i, total); // final tick — guarantees a 100% callback even on a fast/small stream\n\n const customEnvColors: Record<number, {r: number; g: number; b: number}> = {};\n for (const k in header.mCustomEnvColors ?? {}) {\n const c = header.mCustomEnvColors[k as unknown as number];\n customEnvColors[k as unknown as number] = {r: c.r, g: c.g, b: c.b};\n }\n\n const labels: MapData.Label[] = [];\n for (const k in header.labels ?? {}) {\n const areaId = Number(k);\n for (const lb of header.labels[areaId]) {\n labels.push(toMapLabel(convertLabel(lb), areaId));\n }\n }\n\n return {\n kind: \"skeleton\",\n skeleton: {\n count: i, x, y, z, area, env, id, exits,\n areaNames: header.areaNames ?? {}, areaGridMode: buildAreaGridMode(header), customEnvColors,\n names, userData, detailRooms, labels,\n // Already hash -> roomId, exactly the shape SkeletonMapReader wants —\n // no inversion needed (roomIdToHash above is the id -> hash direction,\n // built separately for tagging detailRooms with their hash).\n hashToId: header.mpRoomDbHashToRoomId ?? {},\n },\n };\n}\n\n/**\n * Parse a Mudlet binary `.dat` buffer, picking the loading strategy from its\n * total room count (see {@link LoadMudletMapOptions}). Returns portable data\n * only — call {@link readerFromLoadedMap} to get a live `IMapReader`, or use\n * {@link loadMudletMap} to do both in one call.\n */\nexport function parseMudletMap(bytes: Uint8Array, options: LoadMudletMapOptions = {}): LoadedMudletMap {\n const {mode = \"auto\", threshold = 50_000, onProgress, isDetailRoom} = options;\n const {header, total} = peekHeader(bytes);\n const effectiveMode = mode === \"auto\" ? (total > threshold ? \"streaming\" : \"plain\") : mode;\n return effectiveMode === \"plain\" ? parsePlain(bytes) : parseStreaming(bytes, header, total, onProgress, isDetailRoom);\n}\n\n/** Build the live {@link IMapReader} for data produced by {@link parseMudletMap}. */\nexport function readerFromLoadedMap(loaded: LoadedMudletMap): IMapReader {\n return loaded.kind === \"plain\" ? new MapReader(loaded.map, loaded.envs) : new SkeletonMapReader(loaded.skeleton);\n}\n\n/**\n * Convenience: parse and materialise in one call, on the current thread.\n * `mudlet-map-binary-reader` is a peer dependency; consumers who don't call\n * this never pay the bundle cost. For very large maps, prefer running\n * {@link parseMudletMap} in a Web Worker and calling {@link readerFromLoadedMap}\n * with its result on the main thread.\n */\nexport function loadMudletMap(bytes: Uint8Array, options?: LoadMudletMapOptions): IMapReader {\n return readerFromLoadedMap(parseMudletMap(bytes, options));\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { IMapReader } from '../reader/MapReader';
2
+ /**
3
+ * Headless exporters (`CanvasExporter`, `SvgExporter`) rebuild their scene
4
+ * directly from `state.mapReader`, independent of the live interactive
5
+ * camera. For a viewport-virtualized reader (`SkeletonMapReader`) that reader
6
+ * only materialises rooms inside whatever viewport was LAST pushed onto it —
7
+ * normally by the interactive backend, whose viewport push is itself
8
+ * deferred to a `requestAnimationFrame` (see `KonvaRenderBackend.refresh()`),
9
+ * so an export that runs synchronously right after a camera change (e.g. a
10
+ * `fitArea()` call, as `MapController.renderArea()` does before generating a
11
+ * preview thumbnail) can race it and capture a stale, wrong window instead
12
+ * of the region it was actually asked to export.
13
+ *
14
+ * Pushes a viewport covering exactly `bounds` (world space, the same shape
15
+ * `computeExportBounds` returns) before the caller builds its scene, and
16
+ * returns a restore function that puts back whatever viewport was active
17
+ * before — so the export never leaves the shared reader (and the interactive
18
+ * backend's next incremental refresh) in a different state than it found it.
19
+ */
20
+ export declare function pushExportViewport(mapReader: IMapReader, bounds: {
21
+ x: number;
22
+ y: number;
23
+ w: number;
24
+ h: number;
25
+ }): () => void;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createSettings } from './types/Settings';
2
- export type { Settings, ViewportBounds, RendererEventMap, CullingMode, RoomShape, HiddenRoomMode, LabelRenderMode, PlayerMarkerStyle, RoomClickEventDetail, RoomContextMenuEventDetail, ZoomChangeEventDetail, AreaExitClickEventDetail, PanEventDetail, } from './types/Settings';
2
+ export type { Settings, ViewportBounds, RendererEventMap, CullingMode, RoomShape, HiddenRoomMode, LabelRenderMode, PlayerMarkerStyle, RoomClickEventDetail, RoomContextMenuEventDetail, ZoomChangeEventDetail, AreaExitClickEventDetail, PanEventDetail, LodEventDetail, } from './types/Settings';
3
3
  export { darkenColor, colorLightness, hexToRgba } from './utils/color';
4
4
  export { isRoomHidden, getRoomBorderColor, getRoomBorderThickness, ROOM_UI_HIDDEN, ROOM_UI_BORDER_COLOR, ROOM_UI_BORDER_THICKNESS, } from './scene/RoomFlags';
5
5
  export { MapRenderer } from './rendering/MapRenderer';
@@ -58,6 +58,12 @@ export type { IArea } from './reader/Area';
58
58
  export { default as Plane } from './reader/Plane';
59
59
  export type { IPlane } from './reader/Plane';
60
60
  export type { IExit, Kind as ExitKind } from './reader/Exit';
61
+ export { isViewportDataSource } from './reader/ViewportDataSource';
62
+ export type { ViewportDataSource } from './reader/ViewportDataSource';
63
+ export { isHashLookupCapable } from './reader/HashLookup';
64
+ export type { HashLookupCapable } from './reader/HashLookup';
65
+ export { shouldUseRaster, computeLodMode } from './rendering/lod/lodDecision';
66
+ export type { LodDecisionInput, LodModeInput, LodMode } from './rendering/lod/lodDecision';
61
67
  export type { RoomLens, ExitTreatment } from './lens/RoomLens';
62
68
  export { ALL_VISIBLE, defaultExitTreatment } from './lens/RoomLens';
63
69
  export { composeLenses } from './lens/composeLenses';