Fix NameLayer atlas asset references

This commit is contained in:
scamiv
2026-05-27 16:11:00 +02:00
parent 93445fdfff
commit 5f0fb81758
3 changed files with 170 additions and 7 deletions
+15 -7
View File
@@ -52,6 +52,7 @@ const DYNAMIC_MOVER_ZOOM_HYSTERESIS = 0.2;
const DYNAMIC_MOVER_SCALE_SETTLE_MS = 160;
const DYNAMIC_MOVER_SCALE_COOLDOWN_MS = 300;
const DYNAMIC_MOVER_SUBPIXEL_SNAP = false;
const SPRITELESS_CELL_MARKER_SIZE = 3;
const SMALL_SHIP_MASK_SIZE = 5;
const TRANSPORT_SHIP_MASK = [
"..B..",
@@ -2191,14 +2192,15 @@ export class UnitLayer implements Layer {
customTerritoryColor?: Colord,
): MoverSpriteRect {
if (this.isSpriteLessCellUnit(unit)) {
const outX = roundCoords ? Math.round(x) : x;
const outY = roundCoords ? Math.round(y) : y;
const markerOffset = Math.floor(SPRITELESS_CELL_MARKER_SIZE / 2);
const outX = (roundCoords ? Math.round(x) : x) - markerOffset;
const outY = (roundCoords ? Math.round(y) : y) - markerOffset;
const pad = 2;
return {
x: outX - pad,
y: outY - pad,
w: 1 + pad * 2,
h: 1 + pad * 2,
w: SPRITELESS_CELL_MARKER_SIZE + pad * 2,
h: SPRITELESS_CELL_MARKER_SIZE + pad * 2,
};
}
@@ -2248,10 +2250,16 @@ export class UnitLayer implements Layer {
}
if (this.isSpriteLessCellUnit(unit)) {
const outX = roundCoords ? Math.round(x) : x;
const outY = roundCoords ? Math.round(y) : y;
const markerOffset = Math.floor(SPRITELESS_CELL_MARKER_SIZE / 2);
const outX = (roundCoords ? Math.round(x) : x) - markerOffset;
const outY = (roundCoords ? Math.round(y) : y) - markerOffset;
ctx.fillStyle = this.cellUnitFillStyle(unit);
ctx.fillRect(outX, outY, 1, 1);
ctx.fillRect(
outX,
outY,
SPRITELESS_CELL_MARKER_SIZE,
SPRITELESS_CELL_MARKER_SIZE,
);
ctx.restore();
return this.computeSpriteRect(
+52
View File
@@ -195,11 +195,63 @@ function renderBitmapFontAsset({
);
}
function renderTextureAtlasJsonAsset({
resourcesDir,
relativePath,
assetManifest,
}: DerivedPublicAssetRenderContext): string {
const atlas = JSON.parse(readPublicAssetText(resourcesDir, relativePath)) as {
meta?: { image?: unknown };
};
const imagePath = atlas.meta?.image;
if (imagePath === undefined) {
return `${JSON.stringify(atlas, null, 2)}\n`;
}
if (typeof imagePath !== "string") {
throw new Error(
`Derived asset ${relativePath} contains a non-string atlas image reference`,
);
}
if (imagePath.trim().length === 0) {
throw new Error(
`Derived asset ${relativePath} contains a blank atlas image reference`,
);
}
if (!isExternalAssetReference(imagePath)) {
const referencedAssetPath = resolveDerivedAssetReference(
relativePath,
imagePath,
);
const referencedHashedUrl = assetManifest[referencedAssetPath];
if (!referencedHashedUrl) {
throw new Error(
`Derived asset ${relativePath} references ${referencedAssetPath}, but it is missing from the asset manifest`,
);
}
atlas.meta!.image = getEmittedAssetRelativePath(
relativePath,
referencedHashedUrl,
);
}
return `${JSON.stringify(atlas, null, 2)}\n`;
}
const DERIVED_PUBLIC_ASSET_RENDERERS: DerivedPublicAssetRenderer[] = [
{
matches: (relativePath) => relativePath === "manifest.json",
render: renderWebManifestAsset,
},
{
matches: (relativePath) =>
relativePath.startsWith("images/") && relativePath.endsWith(".json"),
render: renderTextureAtlasJsonAsset,
},
{
matches: (relativePath) =>
relativePath.startsWith("fonts/") && relativePath.endsWith(".xml"),
+103
View File
@@ -59,6 +59,36 @@ describe("PublicAssetManifest", () => {
await fs.writeFile(pagePath, pageContent);
}
async function writeTextureAtlasFixture(
resourcesDir: string,
jsonRelativePath: string,
imageFilePath: string,
imageContent: string = "png-v1",
): Promise<void> {
const jsonPath = path.join(resourcesDir, jsonRelativePath);
const imagePath = path.join(path.dirname(jsonPath), imageFilePath);
const atlasImagePath = imageFilePath.split(path.sep).join(path.posix.sep);
await fs.mkdir(path.dirname(imagePath), { recursive: true });
await fs.writeFile(
jsonPath,
JSON.stringify(
{
frames: {},
meta: {
image: atlasImagePath,
format: "RGBA8888",
size: { w: 1, h: 1 },
scale: "1",
},
},
null,
2,
),
);
await fs.writeFile(imagePath, imageContent);
}
async function emitHashedAsset(
outDir: string,
assetHref: string,
@@ -189,6 +219,79 @@ describe("PublicAssetManifest", () => {
expect(emittedManifest).toContain("data:image/png;base64,AAA");
});
test("rewrites TexturePacker atlas image refs to hashed relative paths", async () => {
const { resourcesDir, outDir } = await createTempResources();
await writeTextureAtlasFixture(
resourcesDir,
path.join("images", "namelayer-icons.json"),
"namelayer-icons.png",
);
const assetManifest = buildPublicAssetManifest([resourcesDir]);
createHashedPublicAssetFiles([resourcesDir], outDir, assetManifest);
const jsonHref = assetManifest["images/namelayer-icons.json"];
const pngHref = assetManifest["images/namelayer-icons.png"];
const emittedJson = await emitHashedAsset(outDir, jsonHref);
const emittedAtlas = JSON.parse(emittedJson) as {
meta: { image: string };
};
expect(emittedAtlas.meta.image).toBe(
getExpectedRelativeEmittedPath(jsonHref, pngHref),
);
expect(emittedAtlas.meta.image).not.toBe("namelayer-icons.png");
});
test("TexturePacker atlas JSON hash changes when its image changes", async () => {
const { resourcesDir } = await createTempResources();
await writeTextureAtlasFixture(
resourcesDir,
path.join("images", "namelayer-icons.json"),
"namelayer-icons.png",
);
const firstManifest = buildPublicAssetManifest([resourcesDir]);
await fs.writeFile(
path.join(resourcesDir, "images", "namelayer-icons.png"),
"png-v2",
);
clearPublicAssetManifestCache();
const secondManifest = buildPublicAssetManifest([resourcesDir]);
expect(firstManifest["images/namelayer-icons.png"]).not.toBe(
secondManifest["images/namelayer-icons.png"],
);
expect(firstManifest["images/namelayer-icons.json"]).not.toBe(
secondManifest["images/namelayer-icons.json"],
);
});
test("fails when TexturePacker atlas JSON references a missing image", async () => {
const { resourcesDir } = await createTempResources();
await fs.mkdir(path.join(resourcesDir, "images"), { recursive: true });
await fs.writeFile(
path.join(resourcesDir, "images", "namelayer-icons.json"),
JSON.stringify(
{
frames: {},
meta: { image: "missing.png" },
},
null,
2,
),
);
expect(() => buildPublicAssetManifest([resourcesDir])).toThrow(
/images\/namelayer-icons\.json references images\/missing\.png/i,
);
});
test("rewrites BMFont XML page filenames to hashed relative paths", async () => {
const { resourcesDir, outDir } = await createTempResources();