0.2.6-dev: the 19 local 玩法说明 pages get a lossless WebP copy in tools/local-extract (25.6 → 17.4 MB; lossy waits for the owner's comparison) and the guide preloads only the next page (host bandwidth layer 0.3); golden: unchanged
This commit is contained in:
1 parent
8edda589f3
commit
05b0dba8b5
7 files changed
+112
-16
No files matched your search
@@ -209,6 +209,47 @@ print(json.dumps(e.prefab_node(go)))`);
|
||||
const raw = spawnSync(PY, ['-c', `import sys; sys.path.insert(0, ${JSON.stringify(TOOL)}); import extract as e, json; print(json.dumps(e.vec(type('V', (), {'x': -0.0, 'y': float('inf'), 'z': 1e-9})())))`], { encoding: 'utf8', env: ENV });
|
||||
assert.equal(raw.stdout.trim(), '[0.0, 0, 0.0]');
|
||||
});
|
||||
|
||||
test('guide pages (host bandwidth layer 0): --webp writes a lossless WebP copy of every page PNG, the manifest lists it, the PNG stays',
|
||||
{ skip: spawnSync(PY, ['-c', "import sys; from PIL import features; sys.exit(0 if features.check('webp') else 1)"], { env: ENV }).status !== 0 && 'no Pillow with WebP' }, () => {
|
||||
const jobs = JSON.parse(spawnSync(PY, [path.join(TOOL, 'extract.py'), '--print-jobs'], { encoding: 'utf8', env: ENV }).stdout);
|
||||
assert.deepEqual(jobs.guideWebp.sub, 'guide');
|
||||
assert.equal(jobs.guideWebp.mode, 'lossless', 'lossy pages wait for the maintainer\'s comparison');
|
||||
assert.ok(!jobs.webp.some((w) => w.sub === 'guide'), 'the board texture table is unchanged');
|
||||
const out = py(`
|
||||
import tempfile, pathlib
|
||||
from PIL import Image
|
||||
root = pathlib.Path(tempfile.mkdtemp())
|
||||
sub = root / 'guide'
|
||||
sub.mkdir()
|
||||
for i, name in enumerate(['autochess_home_1', 'autochess_shop_2']):
|
||||
img = Image.new('RGBA', (48, 32), (10, 20, 30, 255))
|
||||
for x in range(48):
|
||||
for y in range(32): img.putpixel((x, y), ((x * 5 + i * 40) % 256, (y * 7) % 256, (x * y) % 256, 255 if x % 7 else 254))
|
||||
img.save(sub / f'{name}.png')
|
||||
groups = {'guide': {n: {'path': f'/assets/local/guide/{n}.png', 'w': 48, 'h': 32, 'kind': 'Sprite'} for n in ['autochess_home_1', 'autochess_shop_2']},
|
||||
'ui/battle': {'x': {'path': '/assets/local/ui/battle/x.png', 'w': 2, 'h': 2, 'kind': 'Sprite'}}}
|
||||
groups['guide']['autochess_handbook_9'] = {'path': '/assets/local/guide/autochess_handbook_9.png', 'kind': 'Sprite'} # no PNG on disk
|
||||
mf = root / 'local-assets.json'
|
||||
mf.write_text(json.dumps({'version': 1, 'source': 'local-client', 'count': 4, 'groups': groups}))
|
||||
code = e.webp_only(root, mf, lambda *a: None)
|
||||
doc = json.loads(mf.read_text())
|
||||
g = doc['groups']['guide']
|
||||
px = lambda p: list(Image.open(p).convert('RGBA').getdata())
|
||||
print(json.dumps({'code': code, 'paths': {k: v['path'] for k, v in g.items()}, 'modes': {k: v.get('webp') for k, v in g.items()},
|
||||
'other': doc['groups']['ui/battle']['x']['path'],
|
||||
'same': all(px(sub / f'{n}.png') == px(sub / f'{n}.webp') for n in ['autochess_home_1', 'autochess_shop_2']),
|
||||
'png': all((sub / f'{n}.png').exists() for n in ['autochess_home_1', 'autochess_shop_2']),
|
||||
'again': e.run_webp(root, 'guide', doc['groups'], lambda *a: None)}))`);
|
||||
assert.equal(out.code, 0);
|
||||
const at = (n, ext) => `/assets/local/guide/${n}.${ext}`;
|
||||
assert.deepEqual(out.paths, { autochess_home_1: at('autochess_home_1', 'webp'), autochess_shop_2: at('autochess_shop_2', 'webp'), autochess_handbook_9: at('autochess_handbook_9', 'png') });
|
||||
assert.deepEqual(out.modes, { autochess_home_1: 'lossless', autochess_shop_2: 'lossless', autochess_handbook_9: null });
|
||||
assert.equal(out.other, '/assets/local/ui/battle/x.png', 'other groups keep their PNGs');
|
||||
assert.ok(out.same, 'lossless: the same pixels, alpha included');
|
||||
assert.ok(out.png, 'the PNG stays beside the copy');
|
||||
assert.equal(out.again, 2, 'a second run (another GUIDE_WEBP_MODE) rewrites the copies the manifest already lists');
|
||||
});
|
||||
});
|
||||
|
||||
describe('extracted board-scene files (when a local client was extracted)', { skip: !manifest && 'no data/local-assets.json' }, () => {
|
||||
|
||||
@@ -163,6 +163,19 @@ describe('emotes and 玩法说明 pages: local art first, then the mirror copy (
|
||||
assert.ok(!src.includes('该页面暂时无法显示'), 'no blank notice left');
|
||||
});
|
||||
|
||||
test('the guide preloads only the next page (host bandwidth layer 0): wraps around, skips a failed copy, never the previous page', async () => {
|
||||
const { guidePreloadUrl } = await import('../../public/js/ui/guide.js');
|
||||
const page = (k) => ({ urls: [`/assets/local/guide/${k}.webp`, `/assets/ui/guide/${k}.png`] });
|
||||
const pages = ['autochess_home_1', 'autochess_home_2', 'autochess_home_3'].map(page);
|
||||
assert.equal(guidePreloadUrl(pages, 0), '/assets/local/guide/autochess_home_2.webp');
|
||||
assert.equal(guidePreloadUrl(pages, 2), '/assets/local/guide/autochess_home_1.webp', 'wraps to the first page');
|
||||
assert.equal(guidePreloadUrl(pages, 1, new Set(['/assets/local/guide/autochess_home_3.webp'])), '/assets/ui/guide/autochess_home_3.png', 'the copy that has not failed');
|
||||
assert.equal(guidePreloadUrl([page('autochess_home_1')], 0), null, 'a single page needs no preload');
|
||||
assert.equal(guidePreloadUrl([], 0), null);
|
||||
const src = readFileSync(path.join(ROOT, 'public/js/ui/guide.js'), 'utf8');
|
||||
assert.deepEqual(src.match(/^\s+preload\(.*$/gm).map((l) => l.trim()), ['preload(guidePreloadUrl(pages, i, failed));'], 'one preload call in the viewer');
|
||||
});
|
||||
|
||||
test('tools/assets/plan.mjs GUIDE_PAGES is the viewer\'s page list (GUIDE_CHAPTERS), in reading order', async () => {
|
||||
const { GUIDE_CHAPTERS } = await import('../../public/js/ui/guide.js');
|
||||
assert.deepEqual([...GUIDE_PAGES], GUIDE_CHAPTERS.flatMap((c) => c.pages.map(([key]) => key)));
|
||||
|
||||
Reference in new issue
Block a user