@flighthq/tilemap-formats 0.3.0 → 0.3.1-next.1041.35b950c

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.
@@ -1,3 +1,3 @@
1
- import type { TiledMap, TiledTilesetResolver, TilemapData } from '@flighthq/types/contract';
2
- export declare function buildTilemapLayersFromTiled(map: Readonly<TiledMap>, layerIndex: number, resolveTileset: TiledTilesetResolver): TilemapData[] | null;
1
+ import type { ImportDiagnostic, TiledMap, TiledTilesetResolver, TilemapData } from '@flighthq/types/contract';
2
+ export declare function buildTilemapLayersFromTiled(map: Readonly<TiledMap>, layerIndex: number, resolveTileset: TiledTilesetResolver, diagnostics?: ImportDiagnostic[]): TilemapData[] | null;
3
3
  //# sourceMappingURL=tiledProject.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tiledProject.d.ts","sourceRoot":"","sources":["../src/tiledProject.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAmB5F,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,EACvB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,oBAAoB,GACnC,WAAW,EAAE,GAAG,IAAI,CAgDtB"}
1
+ {"version":3,"file":"tiledProject.d.ts","sourceRoot":"","sources":["../src/tiledProject.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAoB9G,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,EACvB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,oBAAoB,EACpC,WAAW,CAAC,EAAE,gBAAgB,EAAE,GAC/B,WAAW,EAAE,GAAG,IAAI,CAmFtB"}
@@ -1,4 +1,6 @@
1
+ import { reportImportDiagnostic } from '@flighthq/importdiagnostics/contract';
1
2
  import { createTilemapData } from '@flighthq/tilemap/contract';
3
+ import { ImportDiagnosticSeverity } from '@flighthq/types/contract';
2
4
  import { decodeTiledGid, getTiledTilesetRefForGid } from './tiledGid';
3
5
  // Projects one Tiled tile layer into runtime `TilemapData`, split by tileset. A Flight `Tilemap`
4
6
  // batches a single tileset per draw, so a layer that draws from N tilesets becomes N single-tileset
@@ -15,7 +17,7 @@ import { decodeTiledGid, getTiledTilesetRefForGid } from './tiledGid';
15
17
  // Returns null when `layerIndex` is out of range or not a tile layer, or when resolution fails
16
18
  // wholesale (no referenced tileset resolves). A ref that resolves to null when others succeed has its
17
19
  // tiles left empty (`-1`) rather than failing the whole projection.
18
- export function buildTilemapLayersFromTiled(map, layerIndex, resolveTileset) {
20
+ export function buildTilemapLayersFromTiled(map, layerIndex, resolveTileset, diagnostics) {
19
21
  const layer = map.layers[layerIndex];
20
22
  if (layer === undefined || layer.type !== 'tilelayer')
21
23
  return null;
@@ -25,13 +27,21 @@ export function buildTilemapLayersFromTiled(map, layerIndex, resolveTileset) {
25
27
  const groups = [];
26
28
  const byFirstGid = new Map();
27
29
  let anyResolved = false;
30
+ // Counted per cell and reported ONCE after the loop. The seam's perf contract forbids a per-element
31
+ // call in a hot loop, and a layer is width x height cells — a map with a broken tileset would
32
+ // otherwise emit one crumb per tile and drown the very report it was meant to make.
33
+ let cellsWithoutTileset = 0;
34
+ let cellsLeftEmpty = 0;
35
+ const unresolvedTilesets = new Set();
28
36
  for (let i = 0; i < cellCount; i++) {
29
37
  const { tileId } = decodeTiledGid(data[i]);
30
38
  if (tileId <= 0)
31
39
  continue;
32
40
  const ref = getTiledTilesetRefForGid(map, tileId);
33
- if (ref === null)
41
+ if (ref === null) {
42
+ cellsWithoutTileset++;
34
43
  continue;
44
+ }
35
45
  let group = byFirstGid.get(ref.firstGid);
36
46
  if (group === undefined) {
37
47
  const layout = resolveTileset(ref);
@@ -47,10 +57,22 @@ export function buildTilemapLayersFromTiled(map, layerIndex, resolveTileset) {
47
57
  groups.push(group);
48
58
  }
49
59
  }
50
- if (group === null)
60
+ if (group === null) {
61
+ cellsLeftEmpty++;
62
+ unresolvedTilesets.add(ref.firstGid);
51
63
  continue;
64
+ }
52
65
  group.tiles[i] = tileId - group.firstGid;
53
66
  }
67
+ // Reported before the wholesale-failure return, so a map that loses SOME tiles and a map that loses
68
+ // all of them are distinguishable: the first returns layers with holes in them, and without this the
69
+ // caller sees a successful projection whose grid is quietly short of the tiles the file authored.
70
+ if (cellsWithoutTileset > 0) {
71
+ reportImportDiagnostic(diagnostics, ImportDiagnosticSeverity.Drop, 'tiled.tile-outside-every-tileset', 'buildTilemapLayersFromTiled', { cells: cellsWithoutTileset, layerIndex });
72
+ }
73
+ if (cellsLeftEmpty > 0) {
74
+ reportImportDiagnostic(diagnostics, ImportDiagnosticSeverity.Drop, 'tiled.tileset-unresolved', 'buildTilemapLayersFromTiled', { cells: cellsLeftEmpty, layerIndex, tilesets: unresolvedTilesets.size });
75
+ }
54
76
  if (!anyResolved)
55
77
  return null;
56
78
  return groups.map((group) => createTilemapData({
@@ -1 +1 @@
1
- {"version":3,"file":"tiledProject.js","sourceRoot":"","sources":["../src/tiledProject.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tiledProject.js","sourceRoot":"","sources":["../src/tiledProject.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/tilemap-formats",
3
- "version": "0.3.0",
3
+ "version": "0.3.1-next.1041.35b950c",
4
4
  "author": "Joshua Granick and other contributors",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -38,9 +38,10 @@
38
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@flighthq/tilemap": "0.3.0",
42
- "@flighthq/types": "0.3.0",
43
- "@flighthq/xml": "0.3.0"
41
+ "@flighthq/importdiagnostics": "0.3.1-next.1041.35b950c",
42
+ "@flighthq/tilemap": "0.3.1-next.1041.35b950c",
43
+ "@flighthq/types": "0.3.1-next.1041.35b950c",
44
+ "@flighthq/xml": "0.3.1-next.1041.35b950c"
44
45
  },
45
46
  "devDependencies": {
46
47
  "typescript": "^5.3.0"
@@ -1,4 +1,4 @@
1
- import type { TiledTilesetResolver } from '@flighthq/types/contract';
1
+ import type { ImportDiagnostic, TiledTilesetResolver } from '@flighthq/types/contract';
2
2
  import { describe, expect, it } from 'vitest';
3
3
 
4
4
  import { buildTilemapLayersFromTiled } from './tiledProject';
@@ -25,6 +25,18 @@ const resolve: TiledTilesetResolver = (ref) => {
25
25
  };
26
26
 
27
27
  describe('buildTilemapLayersFromTiled', () => {
28
+ // A clean parse is two claims: the values are right AND THE PROJECTION IS NOT COMPLAINING. Every other
29
+ // test here checks the first. This checks the second — the one that catches a layer that projected into
30
+ // plausible-looking tile arrays while quietly reporting that it could not place some of them.
31
+ it('raises no diagnostic at all for a well-formed layer', () => {
32
+ const diagnostics: ImportDiagnostic[] = [];
33
+
34
+ buildTilemapLayersFromTiled(mapWithLayerData('1,2,1,2')!, 0, resolve, diagnostics);
35
+
36
+ const complaints = diagnostics.map((diagnostic) => diagnostic.kind);
37
+ expect(complaints, `a good Tiled layer made the projection complain: ${complaints.join(', ')}`).toEqual([]);
38
+ });
39
+
28
40
  it('splits a two-tileset layer into one TilemapData per tileset with local ids', () => {
29
41
  const map = mapWithLayerData('1,5,2147483649,6')!;
30
42
  const result = buildTilemapLayersFromTiled(map, 0, resolve)!;
@@ -57,6 +69,57 @@ describe('buildTilemapLayersFromTiled', () => {
57
69
  expect(Array.from(result[0].tiles)).toEqual([0, -1, 0, -1]);
58
70
  });
59
71
 
72
+ it('reports the tiles an unresolved tileset left empty instead of dropping them silently', () => {
73
+ const diagnostics: ImportDiagnostic[] = [];
74
+ const map = mapWithLayerData('1,5,1,5')!;
75
+ const onlyA: TiledTilesetResolver = (ref) => (ref.firstGid === 1 ? tilesetA : null);
76
+ const result = buildTilemapLayersFromTiled(map, 0, onlyA, diagnostics)!;
77
+
78
+ // The projection SUCCEEDS and the grid looks complete — the holes are -1 cells that no count
79
+ // distinguishes from genuinely empty ones, which is what earns the crumb.
80
+ expect(Array.from(result[0].tiles)).toEqual([0, -1, 0, -1]);
81
+ expect(diagnostics).toMatchObject([
82
+ { detail: { cells: 2, layerIndex: 0, tilesets: 1 }, kind: 'tiled.tileset-unresolved', severity: 'Drop' },
83
+ ]);
84
+ });
85
+
86
+ it('reports a tile whose gid falls outside every declared tileset', () => {
87
+ const diagnostics: ImportDiagnostic[] = [];
88
+ const map = parseTiledTmx(
89
+ '<map version="1" width="2" height="1" tilewidth="16" tileheight="16">' +
90
+ '<tileset firstgid="5" source="b.tsx"/>' +
91
+ '<layer id="1" name="g" width="2" height="1"><data encoding="csv">1,5</data></layer>' +
92
+ '</map>',
93
+ )!;
94
+ buildTilemapLayersFromTiled(map, 0, () => tilesetA, diagnostics);
95
+
96
+ expect(diagnostics).toMatchObject([
97
+ { detail: { cells: 1, layerIndex: 0 }, kind: 'tiled.tile-outside-every-tileset', severity: 'Drop' },
98
+ ]);
99
+ });
100
+
101
+ it('reports once for a whole layer rather than once per cell', () => {
102
+ const diagnostics: ImportDiagnostic[] = [];
103
+ const map = mapWithLayerData('5,5,5,5')!;
104
+ const onlyA: TiledTilesetResolver = (ref) => (ref.firstGid === 1 ? tilesetA : null);
105
+ buildTilemapLayersFromTiled(map, 0, onlyA, diagnostics);
106
+
107
+ // A layer is width x height cells, so a per-cell crumb would drown the report it is making. The
108
+ // count carries the scale instead.
109
+ expect(diagnostics).toHaveLength(1);
110
+ expect(diagnostics[0]!.detail).toMatchObject({ cells: 4 });
111
+ });
112
+
113
+ it('stays silent for a layer whose tilesets all resolve', () => {
114
+ const diagnostics: ImportDiagnostic[] = [];
115
+ const map = mapWithLayerData('1,5,1,5')!;
116
+ const result = buildTilemapLayersFromTiled(map, 0, resolve, diagnostics)!;
117
+
118
+ // Asserting the projected tiles keeps the silence from being vacuous.
119
+ expect(result).toHaveLength(2);
120
+ expect(diagnostics).toEqual([]);
121
+ });
122
+
60
123
  it('returns null when no referenced tileset resolves', () => {
61
124
  const map = mapWithLayerData('1,5,2147483649,6')!;
62
125
  expect(buildTilemapLayersFromTiled(map, 0, () => null)).toBeNull();