pixel-daylight-switch-gsvegetable 3.0.0 → 3.1.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.
- package/README.ja.md +65 -52
- package/README.md +64 -63
- package/README.zh-CN.md +65 -52
- package/app.js +33 -3
- package/browser.mjs +4 -0
- package/docs/CHANGELOG.md +127 -0
- package/docs/GLASS_ENGINE.md +65 -0
- package/{INTEGRATIONS.md → docs/INTEGRATIONS.md} +22 -1
- package/docs/README.md +5 -0
- package/{SCENE_PLUGINS.md → docs/SCENE_PLUGINS.md} +22 -6
- package/examples/scenes/floating-island.v2.json +152 -0
- package/glass-engine.js +162 -0
- package/glass-engine.mjs +4 -0
- package/index.cjs +1 -0
- package/index.d.ts +54 -21
- package/index.mjs +2 -0
- package/llms.txt +21 -15
- package/package.json +21 -8
- package/scene-plugin.js +49 -3
- package/schemas/scene-manifest.v2.schema.json +229 -0
package/index.d.ts
CHANGED
|
@@ -1,38 +1,52 @@
|
|
|
1
1
|
export interface ScenePalette {
|
|
2
2
|
[symbol: string]: `#${string}`;
|
|
3
3
|
}
|
|
4
|
-
|
|
5
|
-
export interface
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
|
|
4
|
+
export interface SceneTimeStop { at: number; palette: string; }
|
|
5
|
+
export interface ScenePixelAsset {
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
/** One printable-ASCII character per pixel. A dot is transparent when used as a layer. */
|
|
9
|
+
pixels: string[];
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export interface SceneLayerAnimation {
|
|
12
|
+
/** drift and bob are deterministic sinusoidal motion tracks. */
|
|
13
|
+
type: 'drift' | 'bob';
|
|
14
|
+
axis: 'x' | 'y';
|
|
15
|
+
/** Angular speed in radians per second, from 0 to 4. */
|
|
16
|
+
speed: number;
|
|
17
|
+
/** Motion amplitude in scene pixels, from 1 to 64. */
|
|
18
|
+
distance: number;
|
|
19
|
+
/** Optional phase in radians, from 0 to 2π. */
|
|
20
|
+
phase?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface SceneLayer {
|
|
23
|
+
id: string;
|
|
24
|
+
asset: string;
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
animation?: SceneLayerAnimation;
|
|
28
|
+
}
|
|
29
|
+
export interface SceneManifestBase {
|
|
15
30
|
id: string;
|
|
16
31
|
name: string;
|
|
17
32
|
author: string;
|
|
18
|
-
/** SPDX identifier or a clearly stated license label. */
|
|
19
33
|
license: string;
|
|
20
34
|
canvas: { width: number; height: number };
|
|
21
|
-
/** One printable-ASCII character per pixel; rows match canvas dimensions. */
|
|
22
35
|
pixels: string[];
|
|
23
|
-
/** Each palette maps every used pixel symbol to a #RGB or #RRGGBB color. */
|
|
24
36
|
palettes: Record<string, ScenePalette>;
|
|
25
|
-
/** Strictly increasing palette stops; must begin at 0 and end at 24. */
|
|
26
37
|
timeCycle: SceneTimeStop[];
|
|
27
38
|
}
|
|
28
|
-
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
/** Manifest v1 preserves the original single pixel-matrix format. */
|
|
40
|
+
export interface SceneManifestV1 extends SceneManifestBase { schemaVersion: 1; }
|
|
41
|
+
/** Manifest v2 adds reusable pixel assets, compositing layers and declarative motion. */
|
|
42
|
+
export interface SceneManifestV2 extends SceneManifestBase {
|
|
43
|
+
schemaVersion: 2;
|
|
44
|
+
assets: Record<string, ScenePixelAsset>;
|
|
45
|
+
layers: SceneLayer[];
|
|
32
46
|
}
|
|
33
|
-
|
|
47
|
+
export type SceneManifest = SceneManifestV1 | SceneManifestV2;
|
|
48
|
+
export interface SceneValidationResult { valid: boolean; errors: string[]; }
|
|
34
49
|
export type UnregisterScene = () => boolean;
|
|
35
|
-
|
|
36
50
|
export interface PixelDaylightSwitchAPI {
|
|
37
51
|
mount(root: Element | null): (() => void) | null;
|
|
38
52
|
unmount(root: Element | null): void;
|
|
@@ -40,8 +54,8 @@ export interface PixelDaylightSwitchAPI {
|
|
|
40
54
|
validateScene(manifest: unknown): SceneValidationResult;
|
|
41
55
|
getScene(id: string): SceneManifest | null;
|
|
42
56
|
listScenes(): SceneManifest[];
|
|
57
|
+
createGlass: typeof createGlass;
|
|
43
58
|
}
|
|
44
|
-
|
|
45
59
|
declare const PixelDaylightSwitch: PixelDaylightSwitchAPI;
|
|
46
60
|
export declare const mount: PixelDaylightSwitchAPI['mount'];
|
|
47
61
|
export declare const unmount: PixelDaylightSwitchAPI['unmount'];
|
|
@@ -50,3 +64,22 @@ export declare const validateScene: PixelDaylightSwitchAPI['validateScene'];
|
|
|
50
64
|
export declare const getScene: PixelDaylightSwitchAPI['getScene'];
|
|
51
65
|
export declare const listScenes: PixelDaylightSwitchAPI['listScenes'];
|
|
52
66
|
export default PixelDaylightSwitch;
|
|
67
|
+
|
|
68
|
+
export type GlassSource = HTMLCanvasElement | OffscreenCanvas | HTMLImageElement | HTMLVideoElement | ImageBitmap | (() => GlassSource);
|
|
69
|
+
export interface GlassEngineOptions {
|
|
70
|
+
source?: GlassSource;
|
|
71
|
+
width?: number | string; height?: number | string;
|
|
72
|
+
left?: number | string; top?: number | string;
|
|
73
|
+
radius?: number | string; refraction?: number; dispersion?: number;
|
|
74
|
+
bezel?: number; shine?: number; opacity?: number; edgeDark?: number;
|
|
75
|
+
ink?: [number, number, number]; light?: [number, number, number];\n shellDay?: [number, number, number]; shellNight?: [number, number, number];\n hiNight?: [number, number, number]; loNight?: [number, number, number]; night?: number;
|
|
76
|
+
dpr?: number; continuous?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface GlassEngineInstance {
|
|
79
|
+
canvas: HTMLCanvasElement;
|
|
80
|
+
setSource(source: GlassSource): this;
|
|
81
|
+
setOptions(options: Partial<GlassEngineOptions>): this;
|
|
82
|
+
render(): this;
|
|
83
|
+
destroy(): void;
|
|
84
|
+
}
|
|
85
|
+
export declare function createGlass(container: HTMLElement, options?: GlassEngineOptions): GlassEngineInstance;
|
package/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import api from './index.cjs';
|
|
2
|
+
import { createGlass } from './glass-engine.mjs';
|
|
2
3
|
|
|
3
4
|
export const mount = api.mount;
|
|
4
5
|
export const unmount = api.unmount;
|
|
@@ -6,4 +7,5 @@ export const registerScene = api.registerScene;
|
|
|
6
7
|
export const validateScene = api.validateScene;
|
|
7
8
|
export const getScene = api.getScene;
|
|
8
9
|
export const listScenes = api.listScenes;
|
|
10
|
+
export { createGlass };
|
|
9
11
|
export default api;
|
package/llms.txt
CHANGED
|
@@ -42,11 +42,17 @@ CommonJS: `const PixelDaylightSwitch = require('pixel-daylight-switch-gsvegetabl
|
|
|
42
42
|
|
|
43
43
|
The package is safe to import during SSR; mount only after the target DOM element exists. `mount(element)` is idempotent and returns a cleanup function on success (or null on failure). `unmount(element)` stops rendering and removes observers/listeners; it does not remove the DOM root or forcibly invalidate the canvas context.
|
|
44
44
|
|
|
45
|
-
Framework lifecycle examples are in [INTEGRATIONS.md](INTEGRATIONS.md). Scene plugin authoring, manifest constraints and API examples are in [SCENE_PLUGINS.md](SCENE_PLUGINS.md).
|
|
45
|
+
Framework lifecycle examples are in [INTEGRATIONS.md](docs/INTEGRATIONS.md). Scene plugin authoring, manifest constraints and API examples are in [SCENE_PLUGINS.md](docs/SCENE_PLUGINS.md).
|
|
46
46
|
|
|
47
|
-
##
|
|
47
|
+
## Standalone refractive glass engine (v3.1.1)
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Import `createGlass` from `pixel-daylight-switch-gsvegetable/glass-engine`, or load `glass-engine.js` directly and use `PixelGlassEngine.createGlass(container, options)`. The independent WebGL2 overlay has configurable width, height, position, radius, refraction, dispersion, bezel, shine, opacity, source and lifecycle methods. Sources are origin-clean canvas, image, video, ImageBitmap or a callback returning one. WebGL cannot sample arbitrary DOM pixels; host applications must supply a renderable background source. See [GLASS_ENGINE.md](docs/GLASS_ENGINE.md) and [the demo](https://gsvegetable.github.io/pixel-daylight-switch/demos/glass-engine.html).
|
|
50
|
+
|
|
51
|
+
## Community scene plugins (current: Manifest v2)
|
|
52
|
+
|
|
53
|
+
The optional `scene-plugin.js` registry exposes `registerScene(manifest)`, `validateScene(manifest)`, `getScene(id)` and `listScenes()`. Register a data-only JSON manifest before mounting the scene. Manifest v2 supports up to 24 reusable pixel-matrix assets, up to 16 ordered layers, transparent overlay pixels (.), and bounded deterministic drift/bob motion tracks. Manifest v1 remains supported and its schema is unchanged. Every symbol in the base matrix and assets must be defined in each palette. Registration validates, copies and freezes the data; arbitrary JavaScript, remote image downloads and executable callbacks are not run.
|
|
54
|
+
|
|
55
|
+
For npm, import `registerScene` from the package. In a browser, load `scene-plugin.js` before `app.js` and register the manifest before mounting. See [the scene authoring guide](docs/SCENE_PLUGINS.md), [Manifest v2 Schema](schemas/scene-manifest.v2.schema.json), [v1 compatibility schema](schemas/scene-manifest.v1.schema.json) and [Floating Island v2 example](examples/scenes/floating-island.v2.json).
|
|
50
56
|
|
|
51
57
|
## Scenes and weather
|
|
52
58
|
|
|
@@ -93,19 +99,19 @@ Invalid values fall back to defaults; documented numeric ranges are clamped.
|
|
|
93
99
|
## Links
|
|
94
100
|
|
|
95
101
|
- [Live demo](https://gsvegetable.github.io/pixel-daylight-switch/)
|
|
96
|
-
- [Interactive Playground](https://gsvegetable.github.io/pixel-daylight-switch/playground.html)
|
|
97
|
-
- [Preview](https://gsvegetable.github.io/pixel-daylight-switch/preview.html)
|
|
98
|
-
- [Integration recipes](INTEGRATIONS.md)
|
|
102
|
+
- [Interactive Playground](https://gsvegetable.github.io/pixel-daylight-switch/demos/playground.html)
|
|
103
|
+
- [Preview](https://gsvegetable.github.io/pixel-daylight-switch/demos/preview.html)
|
|
104
|
+
- [Integration recipes](docs/INTEGRATIONS.md)
|
|
99
105
|
- [English README](README.md) · [简体中文](README.zh-CN.md) · [日本語](README.ja.md)
|
|
100
|
-
- [Changelog](CHANGELOG.md)
|
|
106
|
+
- [Changelog](docs/CHANGELOG.md)
|
|
101
107
|
|
|
102
108
|
|
|
103
|
-
## Scene plugins
|
|
109
|
+
## Scene plugins — current Manifest v2
|
|
104
110
|
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
111
|
+
- Current schema: `schemas/scene-manifest.v2.schema.json`.
|
|
112
|
+
- Example: `examples/scenes/floating-island.v2.json`.
|
|
113
|
+
- Reusable assets: up to 24 pixel matrices; ordered layers: up to 16.
|
|
114
|
+
- Overlay dot (.) is transparent; later layers appear above earlier layers.
|
|
115
|
+
- Motion tracks: bounded deterministic `drift` and `bob`, on x or y.
|
|
116
|
+
- Manifest v1 remains backward compatible; its schema is retained.
|
|
117
|
+
- No arbitrary remote image loading, eval, or executable plugin callbacks.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixel-daylight-switch-gsvegetable",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "3.1.1",
|
|
4
|
+
"description": "A pixel-art day/night switch plus a standalone reusable WebGL2 refractive glass engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pixel-art",
|
|
7
7
|
"day-night",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"scene-plugin",
|
|
18
18
|
"pixel-world",
|
|
19
19
|
"scene-manifest",
|
|
20
|
-
"pixel-art-scenes"
|
|
20
|
+
"pixel-art-scenes",
|
|
21
|
+
"refractive-glass",
|
|
22
|
+
"glass-engine",
|
|
23
|
+
"webgl2-glass",
|
|
24
|
+
"lens-shader"
|
|
21
25
|
],
|
|
22
26
|
"author": "GSvegetable",
|
|
23
27
|
"license": "Unlicense",
|
|
@@ -44,6 +48,11 @@
|
|
|
44
48
|
"./scene-plugin": {
|
|
45
49
|
"import": "./scene-plugin.js",
|
|
46
50
|
"require": "./scene-plugin.js"
|
|
51
|
+
},
|
|
52
|
+
"./glass-engine": {
|
|
53
|
+
"types": "./index.d.ts",
|
|
54
|
+
"import": "./glass-engine.mjs",
|
|
55
|
+
"require": "./glass-engine.js"
|
|
47
56
|
}
|
|
48
57
|
},
|
|
49
58
|
"files": [
|
|
@@ -53,13 +62,17 @@
|
|
|
53
62
|
"README.md",
|
|
54
63
|
"LICENSE",
|
|
55
64
|
"llms.txt",
|
|
56
|
-
"INTEGRATIONS.md",
|
|
57
|
-
"SCENE_PLUGINS.md",
|
|
65
|
+
"docs/INTEGRATIONS.md",
|
|
66
|
+
"docs/SCENE_PLUGINS.md",
|
|
58
67
|
"scene-plugin.js",
|
|
59
68
|
"schemas",
|
|
60
69
|
"examples",
|
|
61
70
|
"browser.mjs",
|
|
62
|
-
"index.d.ts"
|
|
71
|
+
"index.d.ts",
|
|
72
|
+
"docs/CHANGELOG.md",
|
|
73
|
+
"glass-engine.js",
|
|
74
|
+
"docs/GLASS_ENGINE.md",
|
|
75
|
+
"glass-engine.mjs"
|
|
63
76
|
],
|
|
64
77
|
"sideEffects": [
|
|
65
78
|
"./app.js"
|
|
@@ -71,8 +84,8 @@
|
|
|
71
84
|
"access": "public"
|
|
72
85
|
},
|
|
73
86
|
"scripts": {
|
|
74
|
-
"test": "node tests/package-smoke.cjs && node tests/scene-plugin-smoke.cjs && node tests/mobile-smoke.cjs && node tests/embed-smoke.cjs",
|
|
75
|
-
"test:browser": "playwright test",
|
|
87
|
+
"test": "node tests/package-smoke.cjs && node tests/scene-plugin-smoke.cjs && node tests/mobile-smoke.cjs && node tests/embed-smoke.cjs && node tests/glass-engine-smoke.cjs",
|
|
88
|
+
"test:browser": "playwright test --config config/playwright.config.cjs",
|
|
76
89
|
"mobile:prepare": "node scripts/prepare-mobile.cjs",
|
|
77
90
|
"mobile:android": "npm run mobile:prepare && cap sync android && node scripts/set-android-version.cjs android/app/build.gradle",
|
|
78
91
|
"mobile:ios": "npm run mobile:prepare && cap sync ios",
|
package/scene-plugin.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
9
9
|
'use strict';
|
|
10
10
|
const registry = new Map();
|
|
11
|
-
const MAX_WIDTH = 192, MAX_HEIGHT = 112, MAX_PALETTES = 8, MAX_SYMBOLS = 64, MAX_TIME_STOPS = 48;
|
|
11
|
+
const MAX_WIDTH = 192, MAX_HEIGHT = 112, MAX_PALETTES = 8, MAX_SYMBOLS = 64, MAX_TIME_STOPS = 48, MAX_ASSETS = 24, MAX_LAYERS = 16, MAX_TOTAL_CELLS = 430080;
|
|
12
12
|
const ID_RE = /^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$/;
|
|
13
13
|
const COLOR_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
14
14
|
const SYMBOL_RE = /^[\x20-\x7E]$/;
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
function validateScene(input) {
|
|
25
25
|
const errors = [];
|
|
26
26
|
if (!isPlainObject(input)) return { valid:false, errors:['Scene manifest must be a plain object.'] };
|
|
27
|
-
unknownKeys(input, new Set(['schemaVersion','id','name','author','license','canvas','pixels','palettes','timeCycle']), 'manifest', errors);
|
|
28
|
-
if (input.schemaVersion !== 1) errors.push('schemaVersion must be 1.');
|
|
27
|
+
unknownKeys(input, new Set(['schemaVersion','id','name','author','license','canvas','pixels','palettes','timeCycle','assets','layers']), 'manifest', errors);
|
|
28
|
+
if (input.schemaVersion !== 1 && input.schemaVersion !== 2) errors.push('schemaVersion must be 1 or 2.');
|
|
29
|
+
if (input.schemaVersion === 1 && (input.assets !== undefined || input.layers !== undefined)) errors.push('assets and layers require schemaVersion 2.');
|
|
30
|
+
if (input.schemaVersion !== 1 && input.schemaVersion !== 2) errors.push('schemaVersion must be 1 or 2.');
|
|
29
31
|
if (typeof input.id !== 'string' || !ID_RE.test(input.id)) errors.push('id must be 3-64 lowercase letters, numbers, dots, underscores or hyphens.');
|
|
30
32
|
if (BUILT_INS.has(input.id)) errors.push('Built-in scene IDs are reserved.');
|
|
31
33
|
if (typeof input.name !== 'string' || !input.name.trim() || input.name.length > 80) errors.push('name must be a non-empty string of at most 80 characters.');
|
|
@@ -65,6 +67,49 @@
|
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
69
|
}
|
|
70
|
+
const assets = input.assets;
|
|
71
|
+
if (input.schemaVersion === 2) {
|
|
72
|
+
if (!isPlainObject(assets) || Object.keys(assets).length < 1) errors.push('schemaVersion 2 requires at least one named pixel asset.');
|
|
73
|
+
else {
|
|
74
|
+
const names = Object.keys(assets);
|
|
75
|
+
if (names.length > MAX_ASSETS) errors.push('assets may contain at most 24 pixel assets.');
|
|
76
|
+
names.forEach(name => {
|
|
77
|
+
const asset = assets[name];
|
|
78
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,47}$/.test(name)) errors.push('Asset names must be lowercase identifiers.');
|
|
79
|
+
if (!isPlainObject(asset)) { errors.push('Asset "' + name + '" must be an object.'); return; }
|
|
80
|
+
unknownKeys(asset, new Set(['width','height','pixels']), 'assets.' + name, errors);
|
|
81
|
+
if (!Number.isInteger(asset.width) || asset.width < 1 || asset.width > MAX_WIDTH) errors.push('Asset "' + name + '" width must be 1-192.');
|
|
82
|
+
if (!Number.isInteger(asset.height) || asset.height < 1 || asset.height > MAX_HEIGHT) errors.push('Asset "' + name + '" height must be 1-112.');
|
|
83
|
+
if (!Array.isArray(asset.pixels) || asset.pixels.length !== asset.height) errors.push('Asset "' + name + '" pixels must match its height.');
|
|
84
|
+
else asset.pixels.forEach((row,index) => {
|
|
85
|
+
if (typeof row !== 'string' || row.length !== asset.width || !Array.from(row).every(symbol => SYMBOL_RE.test(symbol))) errors.push('Asset "' + name + '" row ' + index + ' must be printable ASCII and match its width.');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(input.layers) || input.layers.length < 1 || input.layers.length > MAX_LAYERS) errors.push('layers must contain 1-16 layer definitions.');
|
|
90
|
+
else input.layers.forEach((layer,index) => {
|
|
91
|
+
if (!isPlainObject(layer)) { errors.push('layers[' + index + '] must be an object.'); return; }
|
|
92
|
+
unknownKeys(layer, new Set(['id','asset','x','y','animation']), 'layers[' + index + ']', errors);
|
|
93
|
+
if (typeof layer.id !== 'string' || !/^[a-z0-9][a-z0-9._-]{0,47}$/.test(layer.id)) errors.push('layers[' + index + '].id must be a lowercase identifier.');
|
|
94
|
+
if (!isPlainObject(assets) || typeof layer.asset !== 'string' || !Object.prototype.hasOwnProperty.call(assets,layer.asset)) errors.push('layers[' + index + '] references an unknown asset.');
|
|
95
|
+
if (!Number.isInteger(layer.x) || layer.x < -MAX_WIDTH || layer.x > MAX_WIDTH) errors.push('layers[' + index + '].x must be an integer from -192 to 192.');
|
|
96
|
+
if (!Number.isInteger(layer.y) || layer.y < -MAX_HEIGHT || layer.y > MAX_HEIGHT) errors.push('layers[' + index + '].y must be an integer from -112 to 112.');
|
|
97
|
+
if (layer.animation !== undefined) {
|
|
98
|
+
const animation = layer.animation;
|
|
99
|
+
if (!isPlainObject(animation)) errors.push('layers[' + index + '].animation must be an object.');
|
|
100
|
+
else {
|
|
101
|
+
unknownKeys(animation,new Set(['type','axis','speed','distance','phase']),'layers[' + index + '].animation',errors);
|
|
102
|
+
if (animation.type !== 'drift' && animation.type !== 'bob') errors.push('Animation type must be drift or bob.');
|
|
103
|
+
if (animation.axis !== 'x' && animation.axis !== 'y') errors.push('Animation axis must be x or y.');
|
|
104
|
+
if (!Number.isFinite(animation.speed) || animation.speed < 0 || animation.speed > 4) errors.push('Animation speed must be between 0 and 4.');
|
|
105
|
+
if (!Number.isInteger(animation.distance) || animation.distance < 1 || animation.distance > 64) errors.push('Animation distance must be an integer from 1 to 64.');
|
|
106
|
+
if (animation.phase !== undefined && (!Number.isFinite(animation.phase) || animation.phase < 0 || animation.phase > 6.2832)) errors.push('Animation phase must be between 0 and 2π.');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const totalCells = Object.values(assets || {}).reduce((sum,asset) => sum + (asset && Number.isInteger(asset.width) && Number.isInteger(asset.height) ? asset.width * asset.height : 0),0);
|
|
111
|
+
if (totalCells > MAX_TOTAL_CELLS) errors.push('Total pixel asset area exceeds the scene budget.');
|
|
112
|
+
}
|
|
68
113
|
const timeCycle = input.timeCycle;
|
|
69
114
|
if (!Array.isArray(timeCycle) || timeCycle.length < 2) errors.push('timeCycle must contain at least two time stops.');
|
|
70
115
|
else {
|
|
@@ -83,6 +128,7 @@
|
|
|
83
128
|
if (Array.isArray(input.pixels) && isPlainObject(palettes)) {
|
|
84
129
|
const symbols = new Set();
|
|
85
130
|
input.pixels.forEach(row => { if (typeof row === 'string') for (const symbol of row) symbols.add(symbol); });
|
|
131
|
+
if (input.schemaVersion === 2 && isPlainObject(assets)) Object.values(assets).forEach(asset => { if (asset && Array.isArray(asset.pixels)) asset.pixels.forEach(row => { if (typeof row === 'string') for (const symbol of row) symbols.add(symbol); }); });
|
|
86
132
|
if (symbols.size > MAX_SYMBOLS) errors.push('pixels cannot use more than 64 unique symbols.');
|
|
87
133
|
Object.keys(palettes).forEach(name => {
|
|
88
134
|
const palette = palettes[name];
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://gsvegetable.github.io/pixel-daylight-switch/schemas/scene-manifest.v2.schema.json",
|
|
4
|
+
"title": "Pixel Daylight Switch Scene Manifest v2",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schemaVersion",
|
|
9
|
+
"id",
|
|
10
|
+
"name",
|
|
11
|
+
"author",
|
|
12
|
+
"license",
|
|
13
|
+
"canvas",
|
|
14
|
+
"pixels",
|
|
15
|
+
"palettes",
|
|
16
|
+
"timeCycle",
|
|
17
|
+
"assets",
|
|
18
|
+
"layers"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"schemaVersion": {
|
|
22
|
+
"const": 2
|
|
23
|
+
},
|
|
24
|
+
"id": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"minLength": 3,
|
|
27
|
+
"maxLength": 64,
|
|
28
|
+
"pattern": "^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$"
|
|
29
|
+
},
|
|
30
|
+
"name": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"minLength": 1,
|
|
33
|
+
"maxLength": 80
|
|
34
|
+
},
|
|
35
|
+
"author": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1,
|
|
38
|
+
"maxLength": 80
|
|
39
|
+
},
|
|
40
|
+
"license": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"minLength": 1,
|
|
43
|
+
"maxLength": 64
|
|
44
|
+
},
|
|
45
|
+
"canvas": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": false,
|
|
48
|
+
"required": [
|
|
49
|
+
"width",
|
|
50
|
+
"height"
|
|
51
|
+
],
|
|
52
|
+
"properties": {
|
|
53
|
+
"width": {
|
|
54
|
+
"type": "integer",
|
|
55
|
+
"minimum": 1,
|
|
56
|
+
"maximum": 192
|
|
57
|
+
},
|
|
58
|
+
"height": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 1,
|
|
61
|
+
"maximum": 112
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"pixels": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"minItems": 1,
|
|
68
|
+
"maxItems": 112,
|
|
69
|
+
"items": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 1,
|
|
72
|
+
"maxLength": 192,
|
|
73
|
+
"pattern": "^[\\x20-\\x7E]+$"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"palettes": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"minProperties": 1,
|
|
79
|
+
"maxProperties": 8,
|
|
80
|
+
"additionalProperties": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"maxProperties": 64,
|
|
83
|
+
"propertyNames": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"minLength": 1,
|
|
86
|
+
"maxLength": 1,
|
|
87
|
+
"pattern": "^[\\u0020-\\u007E]$"
|
|
88
|
+
},
|
|
89
|
+
"additionalProperties": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"timeCycle": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"minItems": 2,
|
|
98
|
+
"items": {
|
|
99
|
+
"type": "object",
|
|
100
|
+
"additionalProperties": false,
|
|
101
|
+
"required": [
|
|
102
|
+
"at",
|
|
103
|
+
"palette"
|
|
104
|
+
],
|
|
105
|
+
"properties": {
|
|
106
|
+
"at": {
|
|
107
|
+
"type": "number",
|
|
108
|
+
"minimum": 0,
|
|
109
|
+
"maximum": 24
|
|
110
|
+
},
|
|
111
|
+
"palette": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"maxItems": 48
|
|
117
|
+
},
|
|
118
|
+
"assets": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"minProperties": 1,
|
|
121
|
+
"maxProperties": 24,
|
|
122
|
+
"additionalProperties": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"additionalProperties": false,
|
|
125
|
+
"required": [
|
|
126
|
+
"width",
|
|
127
|
+
"height",
|
|
128
|
+
"pixels"
|
|
129
|
+
],
|
|
130
|
+
"properties": {
|
|
131
|
+
"width": {
|
|
132
|
+
"type": "integer",
|
|
133
|
+
"minimum": 1,
|
|
134
|
+
"maximum": 192
|
|
135
|
+
},
|
|
136
|
+
"height": {
|
|
137
|
+
"type": "integer",
|
|
138
|
+
"minimum": 1,
|
|
139
|
+
"maximum": 112
|
|
140
|
+
},
|
|
141
|
+
"pixels": {
|
|
142
|
+
"type": "array",
|
|
143
|
+
"minItems": 1,
|
|
144
|
+
"maxItems": 112,
|
|
145
|
+
"items": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"minLength": 1,
|
|
148
|
+
"maxLength": 192,
|
|
149
|
+
"pattern": "^[\\x20-\\x7E]+$"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"layers": {
|
|
156
|
+
"type": "array",
|
|
157
|
+
"minItems": 1,
|
|
158
|
+
"maxItems": 16,
|
|
159
|
+
"items": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"required": [
|
|
163
|
+
"id",
|
|
164
|
+
"asset",
|
|
165
|
+
"x",
|
|
166
|
+
"y"
|
|
167
|
+
],
|
|
168
|
+
"properties": {
|
|
169
|
+
"id": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"pattern": "^[a-z0-9][a-z0-9._-]{0,47}$"
|
|
172
|
+
},
|
|
173
|
+
"asset": {
|
|
174
|
+
"type": "string"
|
|
175
|
+
},
|
|
176
|
+
"x": {
|
|
177
|
+
"type": "integer",
|
|
178
|
+
"minimum": -192,
|
|
179
|
+
"maximum": 192
|
|
180
|
+
},
|
|
181
|
+
"y": {
|
|
182
|
+
"type": "integer",
|
|
183
|
+
"minimum": -112,
|
|
184
|
+
"maximum": 112
|
|
185
|
+
},
|
|
186
|
+
"animation": {
|
|
187
|
+
"type": "object",
|
|
188
|
+
"additionalProperties": false,
|
|
189
|
+
"required": [
|
|
190
|
+
"type",
|
|
191
|
+
"axis",
|
|
192
|
+
"speed",
|
|
193
|
+
"distance"
|
|
194
|
+
],
|
|
195
|
+
"properties": {
|
|
196
|
+
"type": {
|
|
197
|
+
"enum": [
|
|
198
|
+
"drift",
|
|
199
|
+
"bob"
|
|
200
|
+
]
|
|
201
|
+
},
|
|
202
|
+
"axis": {
|
|
203
|
+
"enum": [
|
|
204
|
+
"x",
|
|
205
|
+
"y"
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"speed": {
|
|
209
|
+
"type": "number",
|
|
210
|
+
"minimum": 0,
|
|
211
|
+
"maximum": 4
|
|
212
|
+
},
|
|
213
|
+
"distance": {
|
|
214
|
+
"type": "integer",
|
|
215
|
+
"minimum": 1,
|
|
216
|
+
"maximum": 64
|
|
217
|
+
},
|
|
218
|
+
"phase": {
|
|
219
|
+
"type": "number",
|
|
220
|
+
"minimum": 0,
|
|
221
|
+
"maximum": 6.2832
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|