castle-web-cli 0.4.122 → 0.4.124
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/dist/agent-prompts.js +6 -2
- package/dist/castleJson.d.ts +2 -0
- package/dist/castleJson.js +39 -0
- package/dist/diffText.d.ts +14 -0
- package/dist/diffText.js +138 -0
- package/dist/headlessCover.d.ts +13 -0
- package/dist/headlessCover.js +99 -0
- package/dist/ide.d.ts +2 -1
- package/dist/ide.js +100 -43
- package/dist/imports.js +42 -2
- package/dist/index.js +49 -11
- package/dist/init.d.ts +1 -0
- package/dist/init.js +45 -1
- package/dist/localPaths.d.ts +1 -0
- package/dist/localPaths.js +5 -0
- package/dist/preview.d.ts +0 -1
- package/dist/preview.js +0 -58
- package/dist/save-deck.js +52 -1
- package/dist/serve.js +16 -0
- package/dist/shell/assets/Basteleur-Bold-CK8LF7Pt.woff +0 -0
- package/dist/shell/assets/Basteleur-Bold-DKFKedNb.woff2 +0 -0
- package/dist/shell/assets/index-BfOPkSej.css +1 -0
- package/dist/shell/assets/index-u0nYFqbF.js +434 -0
- package/dist/shell/index.html +2 -2
- package/dist/versionStore.d.ts +54 -0
- package/dist/versionStore.js +281 -0
- package/dist/versions.d.ts +82 -0
- package/dist/versions.js +446 -0
- package/kits/physics-2d/CLAUDE.md +2 -2
- package/kits/physics-2d/castle.json +10 -2
- package/kits/physics-2d/docs/pxart-format.md +33 -26
- package/kits/physics-2d/editors/PxArtEditor.jsx +120 -49
- package/kits/physics-2d/editors/SingleEditor.jsx +6 -3
- package/kits/physics-2d/editors/StyleEditor.jsx +95 -0
- package/kits/physics-2d/editors/pathOverlay.js +1 -1
- package/kits/physics-2d/editors/pathTools.js +9 -1
- package/kits/physics-2d/editors/pixelGeometry.js +14 -13
- package/kits/physics-2d/editors/pixelInspector.jsx +202 -53
- package/kits/physics-2d/editors/pxArtEditorModel.js +8 -63
- package/kits/physics-2d/editors/pxArtTools.js +3 -43
- package/kits/physics-2d/editors/styleEditor.module.css +105 -0
- package/kits/physics-2d/editors/styleTheme.js +16 -0
- package/kits/physics-2d/engine/files.js +2 -1
- package/kits/physics-2d/engine/liveReload.js +4 -3
- package/kits/physics-2d/engine/palettes.js +636 -0
- package/kits/physics-2d/engine/pxart.js +6 -6
- package/kits/physics-2d/engine/svgImport.js +1056 -0
- package/kits/physics-2d/engine/ui.jsx +2 -0
- package/kits/physics-2d/engine/ui.module.css +54 -9
- package/kits/physics-2d/package-lock.json +1 -1
- package/kits/physics-2d/package.json +1 -0
- package/kits/physics-2d/scripts/deckTheme.mjs +25 -0
- package/kits/physics-2d/scripts/draw.mjs +5 -3
- package/kits/physics-2d/scripts/import-svg.mjs +16 -1069
- package/kits/physics-2d/scripts/palette.mjs +10 -0
- package/kits/physics-2d/scripts/svg-emission-guide.md +5 -3
- package/kits/physics-2d/theme.style +3 -0
- package/package.json +1 -1
- package/dist/shell/assets/index-BkJ87APM.css +0 -1
- package/dist/shell/assets/index-DXBpj3-y.js +0 -434
|
@@ -25,7 +25,7 @@ const rawModules = {
|
|
|
25
25
|
{ query: '?raw', import: 'default', eager: true }
|
|
26
26
|
),
|
|
27
27
|
...import.meta.glob(
|
|
28
|
-
['/scenes/**/*.scene', '/blueprints/**/*.scene', '/drawings/**/*.pxart', '/drawings/**/*.sprite', '/behaviors/**/*.jsx'],
|
|
28
|
+
['/scenes/**/*.scene', '/blueprints/**/*.scene', '/drawings/**/*.pxart', '/drawings/**/*.sprite', '/behaviors/**/*.jsx', '/theme.style'],
|
|
29
29
|
{ query: '?raw', import: 'default', eager: true }
|
|
30
30
|
),
|
|
31
31
|
};
|
|
@@ -102,6 +102,7 @@ export function getFileKind(path) {
|
|
|
102
102
|
if (path.endsWith('.scene')) return 'scene';
|
|
103
103
|
if (path.endsWith('.sprite')) return 'pxart';
|
|
104
104
|
if (path.endsWith('.pxart')) return 'pxart';
|
|
105
|
+
if (path.endsWith('.style')) return 'style';
|
|
105
106
|
if (path.endsWith('.png')) return 'image';
|
|
106
107
|
if (path.endsWith('.jpg')) return 'image';
|
|
107
108
|
if (path.endsWith('.jpeg')) return 'image';
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Consumer side of the serve's `files_changed` broadcast. The engine decides
|
|
2
2
|
// what a change means for this bundle:
|
|
3
|
-
// - data files (.scene / .pxart / .drawing) are re-read
|
|
4
|
-
// files API and folded into React state — live re-render,
|
|
3
|
+
// - data files (.scene / .pxart / .sprite / .style / .drawing) are re-read
|
|
4
|
+
// over the serve's files API and folded into React state — live re-render,
|
|
5
|
+
// no reload.
|
|
5
6
|
// - code the bundle imports (per the event's module-graph closure), html,
|
|
6
7
|
// or added/deleted code files (import.meta.glob may pick them up) mean
|
|
7
8
|
// this context is stale — requestReload() (save-state hooks run first).
|
|
@@ -9,7 +10,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
9
10
|
import { onFilesChanged, requestReload } from 'castle-web-sdk';
|
|
10
11
|
import { initialFiles } from './files';
|
|
11
12
|
|
|
12
|
-
const DATA_EXTS = ['.scene', '.pxart', '.sprite', '.drawing'];
|
|
13
|
+
const DATA_EXTS = ['.scene', '.pxart', '.sprite', '.style', '.drawing'];
|
|
13
14
|
const CODE_EXTS = ['.js', '.jsx', '.ts', '.tsx', '.css'];
|
|
14
15
|
|
|
15
16
|
export function isDataFile(path) {
|
|
@@ -0,0 +1,636 @@
|
|
|
1
|
+
// Deck palette resolution + the agent generation subset.
|
|
2
|
+
//
|
|
3
|
+
// A deck may declare a palette in `theme.style`. The editor uses that palette
|
|
4
|
+
// as a picker (the sprite's own palette stays truth). Generation (`draw` /
|
|
5
|
+
// `import-svg`) quantizes to a derived subset so the model stays coherent.
|
|
6
|
+
//
|
|
7
|
+
// Derivation (validated by castle-px-eval): the default Endesga-64 deck keeps
|
|
8
|
+
// the hand-curated AGENT_PALETTE_16; a custom palette larger than 16 is cut
|
|
9
|
+
// by greedy farthest-point in Oklab; 16 or fewer colors are used as-is.
|
|
10
|
+
|
|
11
|
+
import { AGENT_PALETTE_16, EDG64, KEY_ALPHABET, normalizeHex } from './pxart.js';
|
|
12
|
+
|
|
13
|
+
export const THEME_STYLE_PATH = 'theme.style';
|
|
14
|
+
export const OFFICIAL_ENDESGA_64 = 'endesga-64';
|
|
15
|
+
export const PALETTE_KEY_CAP = 64;
|
|
16
|
+
|
|
17
|
+
// Endesga-64 stays first (the default). AAP-64-Castle is the classic Castle
|
|
18
|
+
// editor default (castle-client Palettes.js `default`, not in the GraphQL
|
|
19
|
+
// list). The rest are Castle's published palettes (ghost-server palettes.ts),
|
|
20
|
+
// minus the three already registered from the pixel-art eval (Mushroom /
|
|
21
|
+
// SOBEACHY8 / Grape Soda — those keep the eval hex lists).
|
|
22
|
+
export const OFFICIAL_PALETTES = {
|
|
23
|
+
[OFFICIAL_ENDESGA_64]: {
|
|
24
|
+
id: OFFICIAL_ENDESGA_64,
|
|
25
|
+
name: 'Endesga 64',
|
|
26
|
+
colors: EDG64,
|
|
27
|
+
},
|
|
28
|
+
'aap-64-castle': {
|
|
29
|
+
id: 'aap-64-castle',
|
|
30
|
+
name: 'AAP-64-Castle',
|
|
31
|
+
colors: [
|
|
32
|
+
'#3b1725',
|
|
33
|
+
'#73172d',
|
|
34
|
+
'#b4202a',
|
|
35
|
+
'#df3e23',
|
|
36
|
+
'#fa6a0a',
|
|
37
|
+
'#f9a31b',
|
|
38
|
+
'#ffd541',
|
|
39
|
+
'#fffc40',
|
|
40
|
+
'#fffd98',
|
|
41
|
+
'#d6f264',
|
|
42
|
+
'#9cdb43',
|
|
43
|
+
'#59c135',
|
|
44
|
+
'#14a02e',
|
|
45
|
+
'#1a7a3e',
|
|
46
|
+
'#24523b',
|
|
47
|
+
'#122020',
|
|
48
|
+
'#143464',
|
|
49
|
+
'#285cc4',
|
|
50
|
+
'#249fde',
|
|
51
|
+
'#20d6c7',
|
|
52
|
+
'#a6fcdb',
|
|
53
|
+
'#fff9db',
|
|
54
|
+
'#fef3c0',
|
|
55
|
+
'#fad6b8',
|
|
56
|
+
'#f5a097',
|
|
57
|
+
'#f2838b',
|
|
58
|
+
'#e86a73',
|
|
59
|
+
'#bc4a9b',
|
|
60
|
+
'#793a80',
|
|
61
|
+
'#403353',
|
|
62
|
+
'#242234',
|
|
63
|
+
'#322b28',
|
|
64
|
+
'#71413b',
|
|
65
|
+
'#bb7547',
|
|
66
|
+
'#dba463',
|
|
67
|
+
'#f4d29c',
|
|
68
|
+
'#edeff2',
|
|
69
|
+
'#dae0ea',
|
|
70
|
+
'#b3b9d1',
|
|
71
|
+
'#8b93af',
|
|
72
|
+
'#6d758d',
|
|
73
|
+
'#4a5462',
|
|
74
|
+
'#333941',
|
|
75
|
+
'#422433',
|
|
76
|
+
'#5b3138',
|
|
77
|
+
'#8e5252',
|
|
78
|
+
'#ba756a',
|
|
79
|
+
'#e9b5a3',
|
|
80
|
+
'#e3e6ff',
|
|
81
|
+
'#b9bffb',
|
|
82
|
+
'#849be4',
|
|
83
|
+
'#588dbe',
|
|
84
|
+
'#477d85',
|
|
85
|
+
'#23674e',
|
|
86
|
+
'#328464',
|
|
87
|
+
'#5daf8d',
|
|
88
|
+
'#92dcba',
|
|
89
|
+
'#cdf7e2',
|
|
90
|
+
'#e4d2aa',
|
|
91
|
+
'#c7b08b',
|
|
92
|
+
'#a08662',
|
|
93
|
+
'#796755',
|
|
94
|
+
'#5a4e44',
|
|
95
|
+
'#423934',
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
mushroom: {
|
|
99
|
+
id: 'mushroom',
|
|
100
|
+
name: 'Mushroom',
|
|
101
|
+
colors: [
|
|
102
|
+
'#2e222f',
|
|
103
|
+
'#45293f',
|
|
104
|
+
'#7a3045',
|
|
105
|
+
'#993d41',
|
|
106
|
+
'#cd683d',
|
|
107
|
+
'#fbb954',
|
|
108
|
+
'#f2ec8b',
|
|
109
|
+
'#b0a987',
|
|
110
|
+
'#997f73',
|
|
111
|
+
'#665964',
|
|
112
|
+
'#443846',
|
|
113
|
+
'#576069',
|
|
114
|
+
'#788a87',
|
|
115
|
+
'#a9b2a2',
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
sobeachy8: {
|
|
119
|
+
id: 'sobeachy8',
|
|
120
|
+
name: 'SOBEACHY8',
|
|
121
|
+
colors: [
|
|
122
|
+
'#e55388',
|
|
123
|
+
'#e57d88',
|
|
124
|
+
'#e59f88',
|
|
125
|
+
'#e5d988',
|
|
126
|
+
'#e3d5cc',
|
|
127
|
+
'#bad5cc',
|
|
128
|
+
'#6dd5cc',
|
|
129
|
+
'#5ac5cc',
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
'grape-soda': {
|
|
133
|
+
id: 'grape-soda',
|
|
134
|
+
name: 'Grape Soda',
|
|
135
|
+
colors: [
|
|
136
|
+
'#aa81ff',
|
|
137
|
+
'#b837ea',
|
|
138
|
+
'#aa11bb',
|
|
139
|
+
'#90006c',
|
|
140
|
+
'#56002a',
|
|
141
|
+
'#25000d',
|
|
142
|
+
'#000000',
|
|
143
|
+
'#15021e',
|
|
144
|
+
'#47054f',
|
|
145
|
+
'#c9298c',
|
|
146
|
+
'#ee699e',
|
|
147
|
+
'#ffbac0',
|
|
148
|
+
'#fff6f6',
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
'chocomilk-8': {
|
|
152
|
+
id: 'chocomilk-8',
|
|
153
|
+
name: 'Chocomilk-8',
|
|
154
|
+
colors: [
|
|
155
|
+
'#d6f5e4',
|
|
156
|
+
'#f7ffc5',
|
|
157
|
+
'#b4c788',
|
|
158
|
+
'#8a8969',
|
|
159
|
+
'#8b6a44',
|
|
160
|
+
'#62554c',
|
|
161
|
+
'#463c3c',
|
|
162
|
+
'#312629',
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
'deep-maze': {
|
|
166
|
+
id: 'deep-maze',
|
|
167
|
+
name: 'Deep Maze',
|
|
168
|
+
colors: ['#f2ff66', '#9af089', '#38d88e', '#00be91', '#009a98', '#085562', '#001d2a'],
|
|
169
|
+
},
|
|
170
|
+
'fantasy-24': {
|
|
171
|
+
id: 'fantasy-24',
|
|
172
|
+
name: 'Fantasy 24',
|
|
173
|
+
colors: [
|
|
174
|
+
'#3c9f9c',
|
|
175
|
+
'#276468',
|
|
176
|
+
'#183f39',
|
|
177
|
+
'#1f240a',
|
|
178
|
+
'#39571c',
|
|
179
|
+
'#a58c27',
|
|
180
|
+
'#efac28',
|
|
181
|
+
'#efd8a1',
|
|
182
|
+
'#300f0a',
|
|
183
|
+
'#550f0a',
|
|
184
|
+
'#9b1a0a',
|
|
185
|
+
'#ef3a0c',
|
|
186
|
+
'#ef692f',
|
|
187
|
+
'#efb775',
|
|
188
|
+
'#9c6549',
|
|
189
|
+
'#6f3826',
|
|
190
|
+
'#36170c',
|
|
191
|
+
'#45230d',
|
|
192
|
+
'#724113',
|
|
193
|
+
'#ab5c1c',
|
|
194
|
+
'#2a1d0d',
|
|
195
|
+
'#392a1c',
|
|
196
|
+
'#684c3c',
|
|
197
|
+
'#927e6a',
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
chasm: {
|
|
201
|
+
id: 'chasm',
|
|
202
|
+
name: 'Chasm',
|
|
203
|
+
colors: [
|
|
204
|
+
'#85daeb',
|
|
205
|
+
'#5fc9e7',
|
|
206
|
+
'#5fa1e7',
|
|
207
|
+
'#5f6ee7',
|
|
208
|
+
'#4c60aa',
|
|
209
|
+
'#444774',
|
|
210
|
+
'#32313b',
|
|
211
|
+
'#463c5e',
|
|
212
|
+
'#5d4776',
|
|
213
|
+
'#855395',
|
|
214
|
+
'#ab58a8',
|
|
215
|
+
'#ca60ae',
|
|
216
|
+
'#f3a787',
|
|
217
|
+
'#f5daa7',
|
|
218
|
+
'#8dd894',
|
|
219
|
+
'#5dc190',
|
|
220
|
+
'#4ab9a3',
|
|
221
|
+
'#4593a5',
|
|
222
|
+
'#ffffff',
|
|
223
|
+
'#5efdf7',
|
|
224
|
+
'#ff5dcc',
|
|
225
|
+
'#fdfe89',
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
'db-32': {
|
|
229
|
+
id: 'db-32',
|
|
230
|
+
name: 'DB-32',
|
|
231
|
+
colors: [
|
|
232
|
+
'#000000',
|
|
233
|
+
'#222034',
|
|
234
|
+
'#45283c',
|
|
235
|
+
'#663931',
|
|
236
|
+
'#8f563b',
|
|
237
|
+
'#df7126',
|
|
238
|
+
'#d9a066',
|
|
239
|
+
'#eec39a',
|
|
240
|
+
'#fbf236',
|
|
241
|
+
'#99e550',
|
|
242
|
+
'#6abe30',
|
|
243
|
+
'#37946e',
|
|
244
|
+
'#4b692f',
|
|
245
|
+
'#524b24',
|
|
246
|
+
'#323c39',
|
|
247
|
+
'#3f3f74',
|
|
248
|
+
'#306082',
|
|
249
|
+
'#5b6ee1',
|
|
250
|
+
'#639bff',
|
|
251
|
+
'#5fcde4',
|
|
252
|
+
'#cbdbfc',
|
|
253
|
+
'#ffffff',
|
|
254
|
+
'#9badb7',
|
|
255
|
+
'#847e87',
|
|
256
|
+
'#696a6a',
|
|
257
|
+
'#595652',
|
|
258
|
+
'#76428a',
|
|
259
|
+
'#ac3232',
|
|
260
|
+
'#d95763',
|
|
261
|
+
'#d77bba',
|
|
262
|
+
'#8f974a',
|
|
263
|
+
'#8a6f30',
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
indecision: {
|
|
267
|
+
id: 'indecision',
|
|
268
|
+
name: 'Indecision',
|
|
269
|
+
colors: [
|
|
270
|
+
'#fff4e0',
|
|
271
|
+
'#8fcccb',
|
|
272
|
+
'#449489',
|
|
273
|
+
'#285763',
|
|
274
|
+
'#2f2b5c',
|
|
275
|
+
'#4b3b9c',
|
|
276
|
+
'#457cd6',
|
|
277
|
+
'#f2b63d',
|
|
278
|
+
'#d46e33',
|
|
279
|
+
'#e34262',
|
|
280
|
+
'#94353d',
|
|
281
|
+
'#57253b',
|
|
282
|
+
'#9c656c',
|
|
283
|
+
'#d1b48c',
|
|
284
|
+
'#b4ba47',
|
|
285
|
+
'#6d8c32',
|
|
286
|
+
'#2c1b2e',
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
'devil-food': {
|
|
290
|
+
id: 'devil-food',
|
|
291
|
+
name: 'Devil Food',
|
|
292
|
+
colors: [
|
|
293
|
+
'#160d10',
|
|
294
|
+
'#503146',
|
|
295
|
+
'#6a3c58',
|
|
296
|
+
'#934f65',
|
|
297
|
+
'#7f3265',
|
|
298
|
+
'#6a2249',
|
|
299
|
+
'#912749',
|
|
300
|
+
'#a6333c',
|
|
301
|
+
'#b54846',
|
|
302
|
+
'#bf5e4d',
|
|
303
|
+
'#a13f2f',
|
|
304
|
+
'#7b2c32',
|
|
305
|
+
'#58232f',
|
|
306
|
+
'#351d32',
|
|
307
|
+
'#3e2544',
|
|
308
|
+
'#4a3360',
|
|
309
|
+
'#674268',
|
|
310
|
+
'#b97884',
|
|
311
|
+
'#4b5e7d',
|
|
312
|
+
'#34377e',
|
|
313
|
+
'#2b2948',
|
|
314
|
+
'#1e1c32',
|
|
315
|
+
'#221425',
|
|
316
|
+
'#471f2f',
|
|
317
|
+
'#652c39',
|
|
318
|
+
'#773a40',
|
|
319
|
+
'#8e524c',
|
|
320
|
+
'#5d4143',
|
|
321
|
+
'#3a363c',
|
|
322
|
+
'#37272a',
|
|
323
|
+
'#4b3233',
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
logcabin: {
|
|
327
|
+
id: 'logcabin',
|
|
328
|
+
name: 'Logcabin',
|
|
329
|
+
colors: [
|
|
330
|
+
'#73383e',
|
|
331
|
+
'#ad5845',
|
|
332
|
+
'#cf784c',
|
|
333
|
+
'#e8ad80',
|
|
334
|
+
'#f5e5bf',
|
|
335
|
+
'#f7ffed',
|
|
336
|
+
'#b5f5ec',
|
|
337
|
+
'#5bace3',
|
|
338
|
+
'#636ab8',
|
|
339
|
+
'#574175',
|
|
340
|
+
'#2f2338',
|
|
341
|
+
],
|
|
342
|
+
},
|
|
343
|
+
wisteric: {
|
|
344
|
+
id: 'wisteric',
|
|
345
|
+
name: 'Wisteric',
|
|
346
|
+
colors: [
|
|
347
|
+
'#c9dab8',
|
|
348
|
+
'#86c1a3',
|
|
349
|
+
'#569082',
|
|
350
|
+
'#2d535b',
|
|
351
|
+
'#162841',
|
|
352
|
+
'#100b27',
|
|
353
|
+
'#331541',
|
|
354
|
+
'#732d72',
|
|
355
|
+
'#a75b80',
|
|
356
|
+
'#cd7a87',
|
|
357
|
+
'#f3b5a8',
|
|
358
|
+
'#ffddcb',
|
|
359
|
+
'#e6c9c3',
|
|
360
|
+
'#cea3ad',
|
|
361
|
+
'#b37fa1',
|
|
362
|
+
'#8d5489',
|
|
363
|
+
'#53315a',
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
'interstate-28': {
|
|
367
|
+
id: 'interstate-28',
|
|
368
|
+
name: 'Interstate 28',
|
|
369
|
+
colors: [
|
|
370
|
+
'#fefedf',
|
|
371
|
+
'#f4eaa3',
|
|
372
|
+
'#f4d565',
|
|
373
|
+
'#edb048',
|
|
374
|
+
'#bb5e25',
|
|
375
|
+
'#903a25',
|
|
376
|
+
'#6f1519',
|
|
377
|
+
'#a11920',
|
|
378
|
+
'#da453c',
|
|
379
|
+
'#f2846c',
|
|
380
|
+
'#e8676a',
|
|
381
|
+
'#c14166',
|
|
382
|
+
'#942a5e',
|
|
383
|
+
'#631649',
|
|
384
|
+
'#23192d',
|
|
385
|
+
'#342e4c',
|
|
386
|
+
'#465374',
|
|
387
|
+
'#5a8090',
|
|
388
|
+
'#78ccb9',
|
|
389
|
+
'#57a386',
|
|
390
|
+
'#3a716c',
|
|
391
|
+
'#27424b',
|
|
392
|
+
'#1f2a2c',
|
|
393
|
+
'#474214',
|
|
394
|
+
'#55632e',
|
|
395
|
+
'#89954c',
|
|
396
|
+
'#a1b974',
|
|
397
|
+
'#b6e3b3',
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
function hexToRgb(hex) {
|
|
403
|
+
const h = hex.replace('#', '');
|
|
404
|
+
return [
|
|
405
|
+
parseInt(h.slice(0, 2), 16) / 255,
|
|
406
|
+
parseInt(h.slice(2, 4), 16) / 255,
|
|
407
|
+
parseInt(h.slice(4, 6), 16) / 255,
|
|
408
|
+
];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function linearize(c) {
|
|
412
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function toOklab(hex) {
|
|
416
|
+
const [r, g, b] = hexToRgb(hex).map(linearize);
|
|
417
|
+
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
418
|
+
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
419
|
+
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
420
|
+
const l_ = Math.cbrt(l);
|
|
421
|
+
const m_ = Math.cbrt(m);
|
|
422
|
+
const s_ = Math.cbrt(s);
|
|
423
|
+
return [
|
|
424
|
+
0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
|
|
425
|
+
1.9779984951 * l_ - 2.357995934 * m_ + 0.1802819701 * s_,
|
|
426
|
+
0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_,
|
|
427
|
+
];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function oklabDist(a, b) {
|
|
431
|
+
const [L1, A1, B1] = toOklab(a);
|
|
432
|
+
const [L2, A2, B2] = toOklab(b);
|
|
433
|
+
return Math.hypot(L1 - L2, A1 - A2, B1 - B2);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Greedy farthest-point subset: seed with the two most-distant hexes, then add
|
|
437
|
+
* the hex maximizing min-distance to the chosen set. Deterministic. */
|
|
438
|
+
export function maxDistinctSubset(hexes, n) {
|
|
439
|
+
if (n >= hexes.length) return [...hexes];
|
|
440
|
+
if (n <= 0) return [];
|
|
441
|
+
if (n === 1) return [hexes[0]];
|
|
442
|
+
|
|
443
|
+
let bestI = 0;
|
|
444
|
+
let bestJ = 1;
|
|
445
|
+
let bestD = -1;
|
|
446
|
+
for (let i = 0; i < hexes.length; i++) {
|
|
447
|
+
for (let j = i + 1; j < hexes.length; j++) {
|
|
448
|
+
const d = oklabDist(hexes[i], hexes[j]);
|
|
449
|
+
if (d > bestD) {
|
|
450
|
+
bestD = d;
|
|
451
|
+
bestI = i;
|
|
452
|
+
bestJ = j;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const chosen = [hexes[bestI], hexes[bestJ]];
|
|
458
|
+
const remaining = hexes.filter((_, idx) => idx !== bestI && idx !== bestJ);
|
|
459
|
+
|
|
460
|
+
while (chosen.length < n && remaining.length) {
|
|
461
|
+
let bestIdx = 0;
|
|
462
|
+
let bestMin = -1;
|
|
463
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
464
|
+
const minD = Math.min(...chosen.map((c) => oklabDist(remaining[i], c)));
|
|
465
|
+
if (minD > bestMin) {
|
|
466
|
+
bestMin = minD;
|
|
467
|
+
bestIdx = i;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
chosen.push(remaining.splice(bestIdx, 1)[0]);
|
|
471
|
+
}
|
|
472
|
+
return chosen;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function normalizeColors(raw) {
|
|
476
|
+
if (!Array.isArray(raw)) return [];
|
|
477
|
+
const out = [];
|
|
478
|
+
const seen = new Set();
|
|
479
|
+
for (const entry of raw) {
|
|
480
|
+
let hex = normalizeHex(typeof entry === 'string' ? entry : '');
|
|
481
|
+
if (!hex) continue;
|
|
482
|
+
// A palette entry is a color choice only — opacity is a separate choice in
|
|
483
|
+
// the UX, so #rrggbbaa collapses to its opaque color rather than becoming
|
|
484
|
+
// a distinct swatch. (Sprite files may still carry alpha palette entries.)
|
|
485
|
+
if (hex.length === 9) hex = hex.slice(0, 7);
|
|
486
|
+
if (seen.has(hex)) continue;
|
|
487
|
+
seen.add(hex);
|
|
488
|
+
out.push(hex);
|
|
489
|
+
}
|
|
490
|
+
return out;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function hexSetsEqual(a, b) {
|
|
494
|
+
if (a.length !== b.length) return false;
|
|
495
|
+
const other = new Set(b);
|
|
496
|
+
return a.every((hex) => other.has(hex));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function defaultDeckPalette() {
|
|
500
|
+
return { ...OFFICIAL_PALETTES[OFFICIAL_ENDESGA_64] };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Parse theme.style JSON text. Returns the object or null when empty/invalid. */
|
|
504
|
+
export function parseThemeData(text) {
|
|
505
|
+
if (typeof text !== 'string' || !text.trim()) return null;
|
|
506
|
+
try {
|
|
507
|
+
const value = JSON.parse(text);
|
|
508
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
509
|
+
} catch {
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Resolve a theme.style object to `{ id, name, colors }`. Missing/malformed
|
|
515
|
+
* data falls back to Endesga-64 so default decks stay byte-identical. */
|
|
516
|
+
export function resolveDeckPalette(themeData) {
|
|
517
|
+
const fallback = defaultDeckPalette();
|
|
518
|
+
if (!themeData || typeof themeData !== 'object') return fallback;
|
|
519
|
+
const raw = themeData.palette;
|
|
520
|
+
if (typeof raw === 'string') {
|
|
521
|
+
return OFFICIAL_PALETTES[raw] ? { ...OFFICIAL_PALETTES[raw] } : fallback;
|
|
522
|
+
}
|
|
523
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return fallback;
|
|
524
|
+
if (typeof raw.id === 'string' && OFFICIAL_PALETTES[raw.id] && !Array.isArray(raw.colors)) {
|
|
525
|
+
return { ...OFFICIAL_PALETTES[raw.id] };
|
|
526
|
+
}
|
|
527
|
+
const colors = normalizeColors(raw.colors);
|
|
528
|
+
if (!colors.length) return fallback;
|
|
529
|
+
const name = typeof raw.name === 'string' && raw.name.trim() ? raw.name.trim() : 'custom';
|
|
530
|
+
return { id: null, name, colors };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function isDefaultDeckPalette(palette) {
|
|
534
|
+
return palette?.id === OFFICIAL_ENDESGA_64 || hexSetsEqual(palette?.colors ?? [], EDG64);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** Generation-path subset. Default EDG64 keeps the curated 16; larger custom
|
|
538
|
+
* palettes are cut mechanically; small palettes are used as-is. */
|
|
539
|
+
export function agentPaletteFor(colors, n = 16) {
|
|
540
|
+
const hexes = normalizeColors(colors);
|
|
541
|
+
if (hexSetsEqual(hexes, EDG64)) return [...AGENT_PALETTE_16];
|
|
542
|
+
if (hexes.length > n) return maxDistinctSubset(hexes, n);
|
|
543
|
+
return hexes;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Swatches the editor's deck-palette picker shows. Caps at KEY_ALPHABET's
|
|
547
|
+
* first 64 so a huge uploaded list still fits the key budget. */
|
|
548
|
+
export function pickerColorsFor(colors) {
|
|
549
|
+
const hexes = normalizeColors(colors);
|
|
550
|
+
return hexes.length > PALETTE_KEY_CAP ? maxDistinctSubset(hexes, PALETTE_KEY_CAP) : hexes;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const OFFICIAL_PICKER_PAGES = Object.values(OFFICIAL_PALETTES).map((palette) => ({
|
|
554
|
+
id: palette.id,
|
|
555
|
+
name: palette.name,
|
|
556
|
+
colors: pickerColorsFor(palette.colors),
|
|
557
|
+
}));
|
|
558
|
+
|
|
559
|
+
/** Named pages the sprite/art picker browses. Official palettes first (Endesga
|
|
560
|
+
* leading), then a custom `{ name, colors }` deck page when theme.style isn't
|
|
561
|
+
* an official id. Paging these does not write theme.style. */
|
|
562
|
+
export function pickerPagesFor(deckPalette) {
|
|
563
|
+
if (deckPalette?.id == null && deckPalette?.colors?.length) {
|
|
564
|
+
return [
|
|
565
|
+
...OFFICIAL_PICKER_PAGES,
|
|
566
|
+
{
|
|
567
|
+
id: 'custom',
|
|
568
|
+
name: deckPalette.name || 'custom',
|
|
569
|
+
colors: pickerColorsFor(deckPalette.colors),
|
|
570
|
+
},
|
|
571
|
+
];
|
|
572
|
+
}
|
|
573
|
+
return OFFICIAL_PICKER_PAGES;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Index of the deck's current palette in `pickerPagesFor` output. */
|
|
577
|
+
export function pickerPageIndexFor(pages, deckPalette) {
|
|
578
|
+
const id = deckPalette?.id ?? 'custom';
|
|
579
|
+
const index = pages.findIndex((page) => page.id === id);
|
|
580
|
+
return index >= 0 ? index : 0;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function themeDataFromFiles(files) {
|
|
584
|
+
return parseThemeData(files?.[THEME_STYLE_PATH]);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export function deckPaletteFromFiles(files) {
|
|
588
|
+
return resolveDeckPalette(themeDataFromFiles(files));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** key -> hex record from an ordered `{ key, hex }[]` palette. */
|
|
592
|
+
export function paletteRecord(palette) {
|
|
593
|
+
const rec = {};
|
|
594
|
+
for (const entry of palette ?? []) {
|
|
595
|
+
if (entry?.key && entry.hex) rec[entry.key] = entry.hex;
|
|
596
|
+
}
|
|
597
|
+
return rec;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function hexForPaletteKey(palette, key) {
|
|
601
|
+
if (!key) return null;
|
|
602
|
+
const entry = (palette ?? []).find((item) => item.key === key);
|
|
603
|
+
return entry?.hex ?? null;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function keyForPaletteHex(palette, hex) {
|
|
607
|
+
const norm = normalizeHex(hex ?? '');
|
|
608
|
+
if (!norm) return null;
|
|
609
|
+
const entry = (palette ?? []).find((item) => normalizeHex(item.hex) === norm);
|
|
610
|
+
return entry?.key ?? null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** Find or allocate a sprite-palette key for `hex`. Does not remap existing
|
|
614
|
+
* pixels. Returns `{ sprite, key }` or `{ sprite, key: null, error }`. */
|
|
615
|
+
export function allocateSpriteColor(sprite, hex) {
|
|
616
|
+
const norm = normalizeHex(typeof hex === 'string' ? hex : '');
|
|
617
|
+
if (!norm || !sprite) return { sprite, key: null };
|
|
618
|
+
const existing = keyForPaletteHex(sprite.palette, norm);
|
|
619
|
+
if (existing) return { sprite, key: existing };
|
|
620
|
+
if ((sprite.palette?.length ?? 0) >= PALETTE_KEY_CAP) {
|
|
621
|
+
return { sprite, key: null, error: 'palette-full' };
|
|
622
|
+
}
|
|
623
|
+
const used = new Set((sprite.palette ?? []).map((entry) => entry.key));
|
|
624
|
+
let key = null;
|
|
625
|
+
for (const ch of KEY_ALPHABET) {
|
|
626
|
+
if (!used.has(ch)) {
|
|
627
|
+
key = ch;
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (!key) return { sprite, key: null, error: 'palette-full' };
|
|
632
|
+
return {
|
|
633
|
+
sprite: { ...sprite, palette: [...(sprite.palette ?? []), { key, hex: norm }] },
|
|
634
|
+
key,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
@@ -395,13 +395,13 @@ export function snapResolutionDim(n) {
|
|
|
395
395
|
export const DEFAULT_RESOLUTION = { width: 16, height: 16 };
|
|
396
396
|
|
|
397
397
|
// ---------------------------------------------------------------------------
|
|
398
|
-
//
|
|
398
|
+
// Default deck palette (Endesga-64) + curated agent subset
|
|
399
399
|
//
|
|
400
|
-
// EDG64 is the
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// of EDG64.
|
|
400
|
+
// EDG64 is the default deck palette (Lospec "Endesga 64"). A deck may override
|
|
401
|
+
// it in theme.style — see engine/palettes.js. AGENT_PALETTE_16 is the curated
|
|
402
|
+
// 16-color subset used when the deck palette is EDG64; custom palettes derive
|
|
403
|
+
// their generation subset via agentPaletteFor. Every AGENT_PALETTE_16 color is
|
|
404
|
+
// a member of EDG64.
|
|
405
405
|
// ---------------------------------------------------------------------------
|
|
406
406
|
|
|
407
407
|
/** lospec.com/palette-list/endesga-64 — the full 64-color painting palette.
|