pixel-daylight-switch-gsvegetable 2.2.0 → 3.0.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/INTEGRATIONS.md +139 -0
- package/README.ja.md +54 -2
- package/README.md +77 -2
- package/README.zh-CN.md +54 -2
- package/SCENE_PLUGINS.md +94 -0
- package/app.js +90 -3
- package/browser.mjs +20 -0
- package/examples/scenes/floating-island.v2.json +152 -0
- package/examples/scenes/sakura-evening.json +38 -0
- package/index.cjs +9 -2
- package/index.d.ts +65 -0
- package/index.mjs +4 -0
- package/llms.txt +90 -86
- package/package.json +26 -8
- package/scene-plugin.js +173 -0
- package/schemas/scene-manifest.v1.schema.json +117 -0
- package/schemas/scene-manifest.v2.schema.json +229 -0
package/scene-plugin.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
(function (root, factory) {
|
|
2
|
+
const api = root && root.PixelDaylightSwitchScenePlugins ? root.PixelDaylightSwitchScenePlugins : factory();
|
|
3
|
+
if (typeof module === 'object' && module.exports) module.exports = api;
|
|
4
|
+
if (root) {
|
|
5
|
+
root.PixelDaylightSwitchScenePlugins = api;
|
|
6
|
+
root.PixelDaylightSwitchScenes = api;
|
|
7
|
+
}
|
|
8
|
+
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
const registry = new Map();
|
|
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
|
+
const ID_RE = /^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$/;
|
|
13
|
+
const COLOR_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
14
|
+
const SYMBOL_RE = /^[\x20-\x7E]$/;
|
|
15
|
+
const BUILT_INS = new Set(['countryside', 'japanese-street', 'apartment', 'seaside', 'mountain-cabin']);
|
|
16
|
+
const isPlainObject = value => {
|
|
17
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
18
|
+
const prototype = Object.getPrototypeOf(value);
|
|
19
|
+
return prototype === Object.prototype || prototype === null;
|
|
20
|
+
};
|
|
21
|
+
const unknownKeys = (value, allowed, path, errors) => {
|
|
22
|
+
if (isPlainObject(value)) Object.keys(value).forEach(key => { if (!allowed.has(key)) errors.push(path + ' contains unsupported property "' + key + '".'); });
|
|
23
|
+
};
|
|
24
|
+
function validateScene(input) {
|
|
25
|
+
const errors = [];
|
|
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','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.');
|
|
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.');
|
|
32
|
+
if (BUILT_INS.has(input.id)) errors.push('Built-in scene IDs are reserved.');
|
|
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.');
|
|
34
|
+
if (typeof input.author !== 'string' || !input.author.trim() || input.author.length > 80) errors.push('author must be a non-empty string of at most 80 characters.');
|
|
35
|
+
if (typeof input.license !== 'string' || !input.license.trim() || input.license.length > 64) errors.push('license must be a non-empty SPDX identifier or license label.');
|
|
36
|
+
const canvas = input.canvas;
|
|
37
|
+
if (!isPlainObject(canvas)) errors.push('canvas must be an object.');
|
|
38
|
+
else unknownKeys(canvas, new Set(['width','height']), 'canvas', errors);
|
|
39
|
+
const width = canvas && canvas.width, height = canvas && canvas.height;
|
|
40
|
+
if (!Number.isInteger(width) || width < 1 || width > MAX_WIDTH) errors.push('canvas.width must be an integer from 1 to 192.');
|
|
41
|
+
if (!Number.isInteger(height) || height < 1 || height > MAX_HEIGHT) errors.push('canvas.height must be an integer from 1 to 112.');
|
|
42
|
+
if (!Array.isArray(input.pixels) || input.pixels.length === 0) errors.push('pixels must be a non-empty array of strings.');
|
|
43
|
+
else {
|
|
44
|
+
if (input.pixels.length > MAX_HEIGHT) errors.push('pixels cannot contain more than 112 rows.');
|
|
45
|
+
if (Number.isInteger(height) && input.pixels.length !== height) errors.push('pixels must contain exactly canvas.height rows.');
|
|
46
|
+
input.pixels.forEach((row,index) => {
|
|
47
|
+
if (typeof row !== 'string' || !Number.isInteger(width) || row.length !== width) errors.push('pixels[' + index + '] must be a string exactly canvas.width characters long.');
|
|
48
|
+
else if (!Array.from(row).every(symbol => SYMBOL_RE.test(symbol))) errors.push('pixels[' + index + '] may contain printable ASCII characters only.');
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const palettes = input.palettes;
|
|
52
|
+
if (!isPlainObject(palettes)) errors.push('palettes must be an object of named color maps.');
|
|
53
|
+
else {
|
|
54
|
+
const names = Object.keys(palettes);
|
|
55
|
+
if (!names.length) errors.push('palettes must define at least one palette.');
|
|
56
|
+
if (names.length > MAX_PALETTES) errors.push('palettes cannot contain more than 8 palettes.');
|
|
57
|
+
names.forEach(name => {
|
|
58
|
+
const palette = palettes[name];
|
|
59
|
+
if (!isPlainObject(palette)) errors.push('palette "' + name + '" must be a color map.');
|
|
60
|
+
else {
|
|
61
|
+
const symbols = Object.keys(palette);
|
|
62
|
+
if (symbols.length > MAX_SYMBOLS) errors.push('palette "' + name + '" cannot contain more than 64 symbols.');
|
|
63
|
+
symbols.forEach(symbol => {
|
|
64
|
+
if (!SYMBOL_RE.test(symbol)) errors.push('Palette keys must be one printable ASCII character.');
|
|
65
|
+
if (typeof palette[symbol] !== 'string' || !COLOR_RE.test(palette[symbol])) errors.push('Palette color for "' + symbol + '" must be #RGB or #RRGGBB.');
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
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
|
+
}
|
|
113
|
+
const timeCycle = input.timeCycle;
|
|
114
|
+
if (!Array.isArray(timeCycle) || timeCycle.length < 2) errors.push('timeCycle must contain at least two time stops.');
|
|
115
|
+
else {
|
|
116
|
+
if (timeCycle.length > MAX_TIME_STOPS) errors.push('timeCycle may contain at most 48 stops.');
|
|
117
|
+
let previous = -1;
|
|
118
|
+
timeCycle.forEach((stop,index) => {
|
|
119
|
+
if (!isPlainObject(stop)) { errors.push('timeCycle[' + index + '] must be an object.'); return; }
|
|
120
|
+
unknownKeys(stop, new Set(['at','palette']), 'timeCycle[' + index + ']', errors);
|
|
121
|
+
if (!Number.isFinite(stop.at) || stop.at < 0 || stop.at > 24 || stop.at <= previous) errors.push('timeCycle[' + index + '].at must be strictly increasing from 0 to 24.');
|
|
122
|
+
else previous = stop.at;
|
|
123
|
+
if (typeof stop.palette !== 'string' || !isPlainObject(palettes) || !Object.prototype.hasOwnProperty.call(palettes,stop.palette)) errors.push('timeCycle[' + index + '] references an unknown palette.');
|
|
124
|
+
});
|
|
125
|
+
if (timeCycle[0] && timeCycle[0].at !== 0) errors.push('timeCycle must start at hour 0.');
|
|
126
|
+
if (timeCycle[timeCycle.length-1] && timeCycle[timeCycle.length-1].at !== 24) errors.push('timeCycle must end at hour 24.');
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(input.pixels) && isPlainObject(palettes)) {
|
|
129
|
+
const symbols = new Set();
|
|
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); }); });
|
|
132
|
+
if (symbols.size > MAX_SYMBOLS) errors.push('pixels cannot use more than 64 unique symbols.');
|
|
133
|
+
Object.keys(palettes).forEach(name => {
|
|
134
|
+
const palette = palettes[name];
|
|
135
|
+
if (!isPlainObject(palette)) return;
|
|
136
|
+
symbols.forEach(symbol => { if (!Object.prototype.hasOwnProperty.call(palette,symbol)) errors.push('Palette "' + name + '" is missing pixel symbol "' + symbol + '".'); });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return { valid:errors.length === 0, errors };
|
|
140
|
+
}
|
|
141
|
+
function deepFreeze(value) {
|
|
142
|
+
if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
|
|
143
|
+
Object.keys(value).forEach(key => deepFreeze(value[key]));
|
|
144
|
+
return Object.freeze(value);
|
|
145
|
+
}
|
|
146
|
+
function registerScene(manifest) {
|
|
147
|
+
const result = validateScene(manifest);
|
|
148
|
+
if (!result.valid) {
|
|
149
|
+
const error = new Error('Invalid Pixel Daylight Switch scene: ' + result.errors.join(' '));
|
|
150
|
+
error.validationErrors = result.errors;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
if (registry.has(manifest.id)) throw new Error('A scene with id "' + manifest.id + '" is already registered.');
|
|
154
|
+
let snapshot;
|
|
155
|
+
try { snapshot = JSON.parse(JSON.stringify(manifest)); }
|
|
156
|
+
catch (cause) {
|
|
157
|
+
const error = new Error('Scene manifest must be JSON-serializable.');
|
|
158
|
+
error.cause = cause;
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
const frozen = deepFreeze(snapshot);
|
|
162
|
+
registry.set(frozen.id, frozen);
|
|
163
|
+
let active = true;
|
|
164
|
+
return function unregisterScene() {
|
|
165
|
+
if (!active) return false;
|
|
166
|
+
active = false;
|
|
167
|
+
return registry.delete(frozen.id);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const getScene = id => registry.get(id) || null;
|
|
171
|
+
const listScenes = () => Array.from(registry.values());
|
|
172
|
+
return Object.freeze({ registerScene, validateScene, getScene, listScenes, schemaVersion:1 });
|
|
173
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://gsvegetable.github.io/pixel-daylight-switch/schemas/scene-manifest.v1.schema.json",
|
|
4
|
+
"title": "Pixel Daylight Switch Scene Manifest v1",
|
|
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
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"schemaVersion": {
|
|
20
|
+
"const": 1
|
|
21
|
+
},
|
|
22
|
+
"id": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"minLength": 3,
|
|
25
|
+
"maxLength": 64,
|
|
26
|
+
"pattern": "^[a-z0-9][a-z0-9._-]{1,62}[a-z0-9]$"
|
|
27
|
+
},
|
|
28
|
+
"name": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1,
|
|
31
|
+
"maxLength": 80
|
|
32
|
+
},
|
|
33
|
+
"author": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"minLength": 1,
|
|
36
|
+
"maxLength": 80
|
|
37
|
+
},
|
|
38
|
+
"license": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1,
|
|
41
|
+
"maxLength": 64
|
|
42
|
+
},
|
|
43
|
+
"canvas": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": false,
|
|
46
|
+
"required": [
|
|
47
|
+
"width",
|
|
48
|
+
"height"
|
|
49
|
+
],
|
|
50
|
+
"properties": {
|
|
51
|
+
"width": {
|
|
52
|
+
"type": "integer",
|
|
53
|
+
"minimum": 1,
|
|
54
|
+
"maximum": 192
|
|
55
|
+
},
|
|
56
|
+
"height": {
|
|
57
|
+
"type": "integer",
|
|
58
|
+
"minimum": 1,
|
|
59
|
+
"maximum": 112
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"pixels": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"minItems": 1,
|
|
66
|
+
"maxItems": 112,
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"minLength": 1,
|
|
70
|
+
"maxLength": 192,
|
|
71
|
+
"pattern": "^[\\x20-\\x7E]+$"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"palettes": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"minProperties": 1,
|
|
77
|
+
"maxProperties": 8,
|
|
78
|
+
"additionalProperties": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"maxProperties": 64,
|
|
81
|
+
"propertyNames": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"minLength": 1,
|
|
84
|
+
"maxLength": 1,
|
|
85
|
+
"pattern": "^[\\u0020-\\u007E]$"
|
|
86
|
+
},
|
|
87
|
+
"additionalProperties": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"pattern": "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"timeCycle": {
|
|
94
|
+
"type": "array",
|
|
95
|
+
"minItems": 2,
|
|
96
|
+
"items": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"additionalProperties": false,
|
|
99
|
+
"required": [
|
|
100
|
+
"at",
|
|
101
|
+
"palette"
|
|
102
|
+
],
|
|
103
|
+
"properties": {
|
|
104
|
+
"at": {
|
|
105
|
+
"type": "number",
|
|
106
|
+
"minimum": 0,
|
|
107
|
+
"maximum": 24
|
|
108
|
+
},
|
|
109
|
+
"palette": {
|
|
110
|
+
"type": "string"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"maxItems": 48
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -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
|
+
}
|