cyberia 3.2.5 → 3.2.9
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/.github/workflows/engine-cyberia.cd.yml +2 -2
- package/.github/workflows/release.cd.yml +1 -2
- package/CHANGELOG.md +351 -1
- package/CLI-HELP.md +40 -13
- package/Dockerfile +0 -4
- package/README.md +242 -497
- package/bin/build.js +19 -5
- package/bin/cyberia.js +1149 -240
- package/bin/deploy.js +570 -1
- package/bin/file.js +6 -0
- package/bin/index.js +1149 -240
- package/bin/vs.js +1 -1
- package/conf.js +67 -89
- package/deployment.yaml +4 -222
- package/hardhat/package-lock.json +32 -32
- package/hardhat/package.json +3 -3
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -222
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +10 -118
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +23 -14
- package/proxy.yaml +10 -118
- package/scripts/k3s-node-setup.sh +2 -2
- package/scripts/nat-iptables.sh +103 -18
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +18 -18
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -14
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +76 -21
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/crypto/crypto.controller.js +8 -8
- package/src/api/crypto/crypto.service.js +8 -8
- package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
- package/src/api/cyberia-action/cyberia-action.model.js +87 -0
- package/src/api/cyberia-action/cyberia-action.router.js +27 -0
- package/src/api/cyberia-action/cyberia-action.service.js +42 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +13 -13
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +11 -11
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +16 -16
- package/src/api/cyberia-entity/cyberia-entity.controller.js +10 -10
- package/src/api/cyberia-entity/cyberia-entity.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-fallback-world.js +19 -209
- package/src/api/cyberia-instance/cyberia-instance.controller.js +14 -14
- package/src/api/cyberia-instance/cyberia-instance.model.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +22 -57
- package/src/api/cyberia-instance/cyberia-portal-connector.js +20 -246
- package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +10 -10
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +216 -55
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +4 -1
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +18 -14
- package/src/api/cyberia-map/cyberia-map.controller.js +10 -10
- package/src/api/cyberia-map/cyberia-map.service.js +10 -10
- package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
- package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
- package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/instance/instance.controller.js +10 -10
- package/src/api/instance/instance.model.js +4 -10
- package/src/api/instance/instance.service.js +10 -10
- package/src/api/ipfs/ipfs.controller.js +12 -12
- package/src/api/ipfs/ipfs.model.js +4 -13
- package/src/api/ipfs/ipfs.service.js +14 -28
- package/src/api/object-layer/object-layer.controller.js +12 -12
- package/src/api/object-layer/object-layer.model.js +4 -17
- package/src/api/object-layer/object-layer.service.js +12 -12
- package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
- package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/cluster.js +61 -14
- package/src/cli/db.js +47 -2
- package/src/cli/deploy.js +67 -35
- package/src/cli/fs.js +79 -8
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +26 -1
- package/src/cli/release.js +57 -1
- package/src/cli/repository.js +69 -31
- package/src/cli/run.js +415 -36
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Cryptokoyn.index.js +18 -21
- package/src/client/CyberiaPortal.index.js +19 -23
- package/src/client/Default.index.js +21 -33
- package/src/client/Itemledger.index.js +20 -26
- package/src/client/Underpost.index.js +19 -23
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +35 -37
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +64 -91
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +96 -228
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +462 -178
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +48 -44
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +163 -27
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +57 -57
- package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
- package/src/client/components/cryptokoyn/LogInCryptokoyn.js +6 -4
- package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +6 -4
- package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
- package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
- package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
- package/src/client/components/cyberia/InstanceEngineCyberia.js +141 -60
- package/src/client/components/cyberia/MapEngineCyberia.js +691 -214
- package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +1204 -94
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +196 -298
- package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +102 -102
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +305 -61
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
- package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
- package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
- package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -4
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +57 -57
- package/src/client/components/itemledger/CssItemledger.js +15 -15
- package/src/client/components/itemledger/LogInItemledger.js +6 -4
- package/src/client/components/itemledger/LogOutItemledger.js +6 -4
- package/src/client/components/itemledger/RouterItemledger.js +38 -0
- package/src/client/components/itemledger/SettingsItemledger.js +4 -4
- package/src/client/components/itemledger/SignUpItemledger.js +6 -4
- package/src/client/components/itemledger/TranslateItemledger.js +3 -3
- package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +88 -88
- package/src/client/components/underpost/CssUnderpost.js +14 -14
- package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
- package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
- package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
- package/src/client/components/underpost/LogInUnderpost.js +6 -4
- package/src/client/components/underpost/LogOutUnderpost.js +6 -4
- package/src/client/components/underpost/RouterUnderpost.js +45 -0
- package/src/client/components/underpost/SettingsUnderpost.js +4 -4
- package/src/client/components/underpost/SignUpUnderpost.js +6 -4
- package/src/client/components/underpost/TranslateUnderpost.js +4 -4
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
- package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
- package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
- package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
- package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/crypto/crypto.service.js +8 -13
- package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
- package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +10 -16
- package/src/client/services/cyberia-entity/cyberia-entity.management.js +5 -5
- package/src/client/services/cyberia-entity/cyberia-entity.service.js +10 -16
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +6 -6
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +12 -18
- package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +10 -16
- package/src/client/services/cyberia-map/cyberia-map.management.js +6 -6
- package/src/client/services/cyberia-map/cyberia-map.service.js +12 -18
- package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
- package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
- package/src/client/services/default/default.management.js +159 -267
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/instance/instance.management.js +5 -5
- package/src/client/services/instance/instance.service.js +10 -15
- package/src/client/services/ipfs/ipfs.service.js +12 -18
- package/src/client/services/object-layer/object-layer.management.js +12 -12
- package/src/client/services/object-layer/object-layer.service.js +20 -26
- package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +86 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
- package/src/client/ssr/head/PwaItemledger.js +197 -60
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/grpc/cyberia/grpc-server.js +179 -67
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +80 -0
- package/src/runtime/cyberia-server/Dockerfile +37 -0
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/atlas-sprite-sheet-generator.js +4 -2
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +5 -4
- package/src/server/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/ipfs-client.js +232 -91
- package/src/server/object-layer.js +1 -6
- package/src/server/process.js +13 -27
- package/src/server/semantic-layer-generator-floor.js +11 -51
- package/src/server/semantic-layer-generator-resource.js +259 -0
- package/src/server/semantic-layer-generator-skin.js +41 -171
- package/src/server/semantic-layer-generator.js +122 -14
- package/src/server/shape-generator.js +108 -0
- package/src/server/start.js +17 -3
- package/src/server/valkey.js +141 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.dd-cyberia.json +29 -0
- package/typedoc.json +29 -0
- package/WHITE-PAPER.md +0 -1540
- package/hardhat/README.md +0 -531
- package/hardhat/WHITE-PAPER.md +0 -1540
- package/jsdoc.dd-cyberia.json +0 -68
- package/jsdoc.json +0 -68
- package/src/api/object-layer/README.md +0 -672
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
- package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -62
- package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/components/itemledger/RoutesItemledger.js +0 -40
- package/src/client/components/underpost/RoutesUnderpost.js +0 -47
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
- package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +0 -305
- package/src/grpc/cyberia/README.md +0 -326
|
@@ -21,34 +21,28 @@
|
|
|
21
21
|
*
|
|
22
22
|
* UP direction derives from DOWN but colours the head area with hair instead
|
|
23
23
|
* of skin (the back of the head is visible).
|
|
24
|
-
*
|
|
24
|
+
* The canonical side template is direction 06 (right); LEFT is derived by
|
|
25
|
+
* mirroring it so generated frame keys stay aligned with 04 = left and 06 = right.
|
|
25
26
|
*
|
|
26
27
|
* @module src/server/semantic-layer-generator-skin.js
|
|
27
28
|
* @namespace SemanticLayerGeneratorSkin
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
30
|
import { readFileSync } from 'fs';
|
|
31
31
|
import path from 'path';
|
|
32
32
|
import { fileURLToPath } from 'url';
|
|
33
33
|
import crypto from 'crypto';
|
|
34
|
-
|
|
35
34
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
36
35
|
const TEMPLATES_DIR = path.resolve(__dirname, '../client/public/cyberia/assets/templates');
|
|
37
|
-
|
|
38
36
|
/* ─── Grid dimension (must match GRID_DIM in main generator) ────────────── */
|
|
39
37
|
const SKIN_GRID_DIM = 24;
|
|
40
|
-
|
|
41
38
|
/* ─── Y threshold: pixels at or above this row are considered "head" ─────── */
|
|
42
39
|
const HEAD_Y_MAX = 12;
|
|
43
|
-
|
|
44
40
|
/* ─── Y threshold: pixels at or above (>=) this row are "shoe" zone ──────── */
|
|
45
41
|
const SHOE_Y_MIN = 23;
|
|
46
|
-
|
|
47
42
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
48
43
|
* TEMPLATE LOADING
|
|
49
44
|
* Templates are loaded once at module initialisation (synchronous I/O).
|
|
50
45
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
51
|
-
|
|
52
46
|
/**
|
|
53
47
|
* Loads a JSON template file and returns the parsed value.
|
|
54
48
|
* Returns [] on any error so the generator degrades gracefully.
|
|
@@ -62,7 +56,6 @@ function loadTemplate(filename) {
|
|
|
62
56
|
return [];
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
|
-
|
|
66
59
|
/**
|
|
67
60
|
* Reads a full skin template (item-skin-08.json / item-skin-06.json) and
|
|
68
61
|
* extracts the black (#000000) outline pixels from its `color` grid.
|
|
@@ -90,7 +83,6 @@ function loadBorderFromTemplate(filename) {
|
|
|
90
83
|
return [];
|
|
91
84
|
}
|
|
92
85
|
}
|
|
93
|
-
|
|
94
86
|
/**
|
|
95
87
|
* Raw template pixel lists keyed by direction and zone name.
|
|
96
88
|
* Coordinates are [x, y] pairs where (0,0) is the top-left cell of a
|
|
@@ -98,8 +90,8 @@ function loadBorderFromTemplate(filename) {
|
|
|
98
90
|
*
|
|
99
91
|
* @type {{ down: Object, left: Object }}
|
|
100
92
|
*/
|
|
101
|
-
|
|
102
|
-
down
|
|
93
|
+
class RAW {
|
|
94
|
+
static down = {
|
|
103
95
|
/** Full body silhouette – face, torso, arms, legs */
|
|
104
96
|
skin: loadTemplate('item-skin-style-skin-08.json'),
|
|
105
97
|
/** Shirt / breastplate zone */
|
|
@@ -110,16 +102,15 @@ const RAW = {
|
|
|
110
102
|
hair: loadTemplate('item-skin-style-hair.json'),
|
|
111
103
|
/** Black outline border from full template color grid */
|
|
112
104
|
border: loadBorderFromTemplate('item-skin-08.json'),
|
|
113
|
-
}
|
|
114
|
-
left
|
|
105
|
+
};
|
|
106
|
+
static left = {
|
|
115
107
|
skin: loadTemplate('item-skin-style-skin-06.json'),
|
|
116
108
|
shirt: loadTemplate('item-skin-style-breastplate-06.json'),
|
|
117
109
|
legs: loadTemplate('item-skin-style-legs-06.json'),
|
|
118
110
|
hair: loadTemplate('item-skin-style-hair.json'),
|
|
119
111
|
border: loadBorderFromTemplate('item-skin-06.json'),
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
112
|
+
};
|
|
113
|
+
}
|
|
123
114
|
/**
|
|
124
115
|
* Splits the legs template into pants (y < SHOE_Y_MIN) and shoes (y >= SHOE_Y_MIN).
|
|
125
116
|
* @param {number[][]} legsCoords
|
|
@@ -134,7 +125,6 @@ function splitLegs(legsCoords) {
|
|
|
134
125
|
}
|
|
135
126
|
return { pants, shoes };
|
|
136
127
|
}
|
|
137
|
-
|
|
138
128
|
/**
|
|
139
129
|
* Mirrors a pixel list horizontally so a left-facing sprite becomes right-facing.
|
|
140
130
|
* Uses the reference width 26 (template grid): x_new = 25 − x.
|
|
@@ -147,7 +137,6 @@ function mirrorH(coords) {
|
|
|
147
137
|
.map(([x, y]) => [25 - x, y])
|
|
148
138
|
.filter(([x, y]) => x >= 0 && x < SKIN_GRID_DIM && y >= 0 && y < SKIN_GRID_DIM);
|
|
149
139
|
}
|
|
150
|
-
|
|
151
140
|
/**
|
|
152
141
|
* Per-direction pixel zone definitions.
|
|
153
142
|
* Shoes are separated from pants; hair and skin are provided per direction.
|
|
@@ -160,33 +149,27 @@ const ZONES = (() => {
|
|
|
160
149
|
const { pants, shoes } = splitLegs(RAW.down.legs);
|
|
161
150
|
return { skin: RAW.down.skin, shirt: RAW.down.shirt, pants, shoes, hair: RAW.down.hair, border: RAW.down.border };
|
|
162
151
|
})();
|
|
163
|
-
|
|
164
|
-
const left = (() => {
|
|
152
|
+
const right = (() => {
|
|
165
153
|
const { pants, shoes } = splitLegs(RAW.left.legs);
|
|
166
154
|
return { skin: RAW.left.skin, shirt: RAW.left.shirt, pants, shoes, hair: RAW.left.hair, border: RAW.left.border };
|
|
167
155
|
})();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
border: mirrorH(left.border),
|
|
156
|
+
const left = {
|
|
157
|
+
skin: mirrorH(right.skin),
|
|
158
|
+
shirt: mirrorH(right.shirt),
|
|
159
|
+
pants: mirrorH(right.pants),
|
|
160
|
+
shoes: mirrorH(right.shoes),
|
|
161
|
+
hair: mirrorH(right.hair),
|
|
162
|
+
border: mirrorH(right.border),
|
|
176
163
|
};
|
|
177
|
-
|
|
178
164
|
// UP: same body layout as DOWN but head pixels (y <= HEAD_Y_MAX) are painted
|
|
179
165
|
// with hair color (back of character's head), body/arms remain skin colored.
|
|
180
166
|
// Reuse down zones; we flag this dynamically in buildDirectionMatrix.
|
|
181
167
|
const up = { ...down, isUpDirection: true };
|
|
182
|
-
|
|
183
168
|
return { down, left, right, up };
|
|
184
169
|
})();
|
|
185
|
-
|
|
186
170
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
187
171
|
* COLOUR UTILITIES
|
|
188
172
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
189
|
-
|
|
190
173
|
/**
|
|
191
174
|
* Mini LCG-based RNG (32-bit seed). Avoids importing from the parent module.
|
|
192
175
|
* @param {number} seed
|
|
@@ -199,7 +182,6 @@ function lcgRng(seed) {
|
|
|
199
182
|
return s / 4294967296;
|
|
200
183
|
};
|
|
201
184
|
}
|
|
202
|
-
|
|
203
185
|
/**
|
|
204
186
|
* Simple string hash → 32-bit unsigned integer.
|
|
205
187
|
* @param {string} str
|
|
@@ -210,7 +192,6 @@ function hashStr(str) {
|
|
|
210
192
|
for (let i = 0; i < str.length; i++) h = (Math.imul(31, h) + str.charCodeAt(i)) | 0;
|
|
211
193
|
return h >>> 0;
|
|
212
194
|
}
|
|
213
|
-
|
|
214
195
|
/**
|
|
215
196
|
* HSL colour to [r, g, b] (each 0–255).
|
|
216
197
|
* @param {number} h Hue in [0, 1).
|
|
@@ -230,17 +211,14 @@ function hslToRgb(h, s, l) {
|
|
|
230
211
|
};
|
|
231
212
|
return [Math.round(hue2rgb(h + 1 / 3) * 255), Math.round(hue2rgb(h) * 255), Math.round(hue2rgb(h - 1 / 3) * 255)];
|
|
232
213
|
}
|
|
233
|
-
|
|
234
214
|
/** Clamp v to [lo, hi]. */
|
|
235
215
|
function clamp(v, lo, hi) {
|
|
236
216
|
return v < lo ? lo : v > hi ? hi : v;
|
|
237
217
|
}
|
|
238
|
-
|
|
239
218
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
240
219
|
* PALETTE DERIVATION
|
|
241
220
|
* All colours are deterministic from (seed, itemId).
|
|
242
221
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
243
|
-
|
|
244
222
|
/**
|
|
245
223
|
* @typedef {Object} SkinPalette
|
|
246
224
|
* @property {number[]} skinColor RGBA – face / hands / neck
|
|
@@ -250,7 +228,6 @@ function clamp(v, lo, hi) {
|
|
|
250
228
|
* @property {number[]} shoeColor RGBA – shoes / boots
|
|
251
229
|
* @property {number} hairDepth Max Y row (inclusive) covered by hair; controls hair length.
|
|
252
230
|
*/
|
|
253
|
-
|
|
254
231
|
/**
|
|
255
232
|
* Derives a fully deterministic 5-colour palette for a skin variant.
|
|
256
233
|
*
|
|
@@ -261,7 +238,6 @@ function clamp(v, lo, hi) {
|
|
|
261
238
|
*/
|
|
262
239
|
function deriveSkinPalette(seed, itemId, subtype = 'random') {
|
|
263
240
|
const rng = lcgRng(hashStr(`${seed}:${itemId}:skin-palette`));
|
|
264
|
-
|
|
265
241
|
/* ── Skin tones: dark brown → light peach ───────────────────────────── */
|
|
266
242
|
const allSkinTones = [
|
|
267
243
|
[38, 22, 14], // near-black
|
|
@@ -277,7 +253,6 @@ function deriveSkinPalette(seed, itemId, subtype = 'random') {
|
|
|
277
253
|
const skinPool =
|
|
278
254
|
subtype === 'dark' ? allSkinTones.slice(0, 3) : subtype === 'light' ? allSkinTones.slice(5) : allSkinTones;
|
|
279
255
|
const skinRgb = skinPool[Math.floor(rng() * skinPool.length)];
|
|
280
|
-
|
|
281
256
|
/* ── Hair: natural shades + vivid options ───────────────────────────── */
|
|
282
257
|
const naturalHair = [
|
|
283
258
|
[18, 12, 8], // near-black
|
|
@@ -302,21 +277,17 @@ function deriveSkinPalette(seed, itemId, subtype = 'random') {
|
|
|
302
277
|
const allHairPresets = [...naturalHair, ...vividHair];
|
|
303
278
|
const hairPool = subtype === 'vivid' ? vividHair : subtype === 'natural' ? naturalHair : allHairPresets;
|
|
304
279
|
const hairRgb = hairPool[Math.floor(rng() * hairPool.length)];
|
|
305
|
-
|
|
306
280
|
/* ── Clothing: random hue, reasonable saturation/lightness ─────────── */
|
|
307
281
|
const shirtRgb = hslToRgb(rng(), 0.6 + rng() * 0.35, 0.32 + rng() * 0.28);
|
|
308
282
|
const pantsRgb = hslToRgb(rng(), 0.55 + rng() * 0.4, 0.2 + rng() * 0.3);
|
|
309
|
-
|
|
310
283
|
/* Shoes tend to be darker (lower lightness) */
|
|
311
284
|
const shoeH = rng();
|
|
312
285
|
const shoeRgb = hslToRgb(shoeH, 0.35 + rng() * 0.4, 0.12 + rng() * 0.2);
|
|
313
|
-
|
|
314
286
|
/* ── Hair depth: controls how far down the hair goes on the head ───── *
|
|
315
287
|
* 0 → shaved (no hair at all — bald silhouette) *
|
|
316
288
|
* 5 – 11 → short crop to long flowing hair */
|
|
317
289
|
const hairDepthOptions = subtype === 'shaved' ? [0] : [5, 6, 7, 8, 9, 10, 11];
|
|
318
290
|
const hairDepth = hairDepthOptions[Math.floor(rng() * hairDepthOptions.length)];
|
|
319
|
-
|
|
320
291
|
return {
|
|
321
292
|
skinColor: [...skinRgb, 255],
|
|
322
293
|
hairColor: [...hairRgb, 255],
|
|
@@ -326,11 +297,9 @@ function deriveSkinPalette(seed, itemId, subtype = 'random') {
|
|
|
326
297
|
hairDepth,
|
|
327
298
|
};
|
|
328
299
|
}
|
|
329
|
-
|
|
330
300
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
331
301
|
* FRAME MATRIX BUILDER
|
|
332
302
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
333
|
-
|
|
334
303
|
/**
|
|
335
304
|
* Returns the index of `rgba` in `globalColors`, adding it if absent.
|
|
336
305
|
* @param {number[][]} globalColors
|
|
@@ -345,7 +314,6 @@ function getOrAddColor(globalColors, rgba) {
|
|
|
345
314
|
globalColors.push([...rgba]);
|
|
346
315
|
return globalColors.length - 1;
|
|
347
316
|
}
|
|
348
|
-
|
|
349
317
|
/**
|
|
350
318
|
* Paints a list of [x,y] coordinates onto a frame matrix with a color index.
|
|
351
319
|
* Skips coordinates outside SKIN_GRID_DIM.
|
|
@@ -360,7 +328,6 @@ function paint(matrix, coords, colorIdx) {
|
|
|
360
328
|
}
|
|
361
329
|
}
|
|
362
330
|
}
|
|
363
|
-
|
|
364
331
|
/**
|
|
365
332
|
* Builds a 24 × 24 frame matrix for DOWN, LEFT, or RIGHT directions.
|
|
366
333
|
*
|
|
@@ -384,25 +351,20 @@ function paint(matrix, coords, colorIdx) {
|
|
|
384
351
|
*/
|
|
385
352
|
function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLabel) {
|
|
386
353
|
const matrix = Array.from({ length: SKIN_GRID_DIM }, () => Array(SKIN_GRID_DIM).fill(0));
|
|
387
|
-
|
|
388
354
|
const skinIdx = getOrAddColor(globalColors, palette.skinColor);
|
|
389
355
|
const hairIdx = getOrAddColor(globalColors, palette.hairColor);
|
|
390
356
|
const shirtIdx = getOrAddColor(globalColors, palette.shirtColor);
|
|
391
357
|
const pantsIdx = getOrAddColor(globalColors, palette.pantsColor);
|
|
392
358
|
const shoeIdx = getOrAddColor(globalColors, palette.shoeColor);
|
|
393
|
-
|
|
394
359
|
// Sets for fast per-pixel lookup
|
|
395
360
|
const borderSet = new Set(zones.border.map(([x, y]) => `${x},${y}`));
|
|
396
361
|
const skinSet = new Set(zones.skin.map(([x, y]) => `${x},${y}`));
|
|
397
|
-
|
|
398
362
|
// 1. Full body silhouette → skin tone
|
|
399
363
|
paint(matrix, zones.skin, skinIdx);
|
|
400
|
-
|
|
401
364
|
// Compute hair pixels and per-row bounding boxes once — shared by steps 2b and 2c.
|
|
402
365
|
// hairDepth === 0 → shaved head, all hair steps are skipped.
|
|
403
366
|
const hairPixels =
|
|
404
367
|
palette.hairDepth > 0 ? zones.skin.filter(([x, y]) => y <= palette.hairDepth && !borderSet.has(`${x},${y}`)) : [];
|
|
405
|
-
|
|
406
368
|
const hairRowBounds = new Map();
|
|
407
369
|
for (const [x, y] of hairPixels) {
|
|
408
370
|
const b = hairRowBounds.get(y) || { min: x, max: x };
|
|
@@ -411,19 +373,15 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
411
373
|
hairRowBounds.set(y, b);
|
|
412
374
|
}
|
|
413
375
|
const hairRows = [...hairRowBounds.keys()].sort((a, b) => a - b);
|
|
414
|
-
|
|
415
376
|
if (palette.hairDepth > 0) {
|
|
416
377
|
// 2. Hair fill
|
|
417
378
|
paint(matrix, hairPixels, hairIdx);
|
|
418
|
-
|
|
419
379
|
const blackIdx = getOrAddColor(globalColors, [0, 0, 0, 255]);
|
|
420
|
-
|
|
421
380
|
// 2b. Black hairline + bang wisps at the fringe (bottom of hair zone).
|
|
422
381
|
if (hairRows.length > 0) {
|
|
423
382
|
const rngFringe = lcgRng(hashStr(`${seed}:${itemId}:fringe-${dirLabel}`));
|
|
424
383
|
const bottomY = hairRows[hairRows.length - 1];
|
|
425
384
|
const { min: fMin, max: fMax } = hairRowBounds.get(bottomY);
|
|
426
|
-
|
|
427
385
|
// Build 2–4 random bang-wisp columns
|
|
428
386
|
const fringeCols = [];
|
|
429
387
|
for (let x = fMin; x <= fMax; x++) fringeCols.push(x);
|
|
@@ -449,7 +407,6 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
449
407
|
matrix[tipY][wx] = blackIdx;
|
|
450
408
|
}
|
|
451
409
|
}
|
|
452
|
-
|
|
453
410
|
// Black hairline for non-wisp fringe columns
|
|
454
411
|
const fringeY = bottomY + 1;
|
|
455
412
|
if (fringeY < SKIN_GRID_DIM) {
|
|
@@ -460,7 +417,6 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
460
417
|
}
|
|
461
418
|
}
|
|
462
419
|
}
|
|
463
|
-
|
|
464
420
|
// 2c. SIDE HAIR STRANDS — 1-px strands at each temple, anchored at the
|
|
465
421
|
// outermost border columns of the head at HEAD_Y_MAX (ear level).
|
|
466
422
|
// Fixed anchor means position is independent of hairDepth, giving a
|
|
@@ -472,36 +428,28 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
472
428
|
{
|
|
473
429
|
const rngWisp = lcgRng(hashStr(`${seed}:${itemId}:outer-wisp-${dirLabel}`));
|
|
474
430
|
const strandRows = palette.hairDepth >= 5 ? Math.min(palette.hairDepth - 4, 3) : 0;
|
|
475
|
-
|
|
476
431
|
if (strandRows > 0) {
|
|
477
432
|
const borderAtHead = zones.border.filter(([, y]) => y === HEAD_Y_MAX);
|
|
478
433
|
if (borderAtHead.length >= 2) {
|
|
479
434
|
const hbL = Math.min(...borderAtHead.map(([x]) => x));
|
|
480
435
|
const hbR = Math.max(...borderAtHead.map(([x]) => x));
|
|
481
|
-
|
|
482
436
|
let lastLx = null,
|
|
483
437
|
lastRx = null;
|
|
484
|
-
|
|
485
438
|
for (let row = 0; row < strandRows; row++) {
|
|
486
439
|
const y = HEAD_Y_MAX + 1 + row; // just below ear level
|
|
487
440
|
if (y >= SKIN_GRID_DIM) break;
|
|
488
441
|
const ext = rngWisp() < 0.3 ? 2 : 1;
|
|
489
|
-
|
|
490
442
|
const lMin = Math.max(0, hbL - ext);
|
|
491
443
|
const rMax = Math.min(SKIN_GRID_DIM - 1, hbR + ext);
|
|
492
|
-
|
|
493
444
|
// Left strand: pixels outside the body to the left of hbL
|
|
494
445
|
for (let x = lMin; x < hbL; x++) matrix[y][x] = hairIdx;
|
|
495
446
|
// Right strand: pixels outside the body to the right of hbR
|
|
496
447
|
for (let x = hbR + 1; x <= rMax; x++) matrix[y][x] = hairIdx;
|
|
497
|
-
|
|
498
448
|
if (lMin > 0 && matrix[y][lMin - 1] !== hairIdx) matrix[y][lMin - 1] = blackIdx;
|
|
499
449
|
if (rMax < SKIN_GRID_DIM - 1 && matrix[y][rMax + 1] !== hairIdx) matrix[y][rMax + 1] = blackIdx;
|
|
500
|
-
|
|
501
450
|
lastLx = lMin;
|
|
502
451
|
lastRx = rMax;
|
|
503
452
|
}
|
|
504
|
-
|
|
505
453
|
const tipY = HEAD_Y_MAX + 1 + strandRows;
|
|
506
454
|
if (tipY < SKIN_GRID_DIM) {
|
|
507
455
|
if (lastLx !== null) matrix[tipY][Math.max(0, lastLx)] = blackIdx;
|
|
@@ -510,7 +458,6 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
510
458
|
}
|
|
511
459
|
}
|
|
512
460
|
}
|
|
513
|
-
|
|
514
461
|
// 2d. UPPER SIDE HAIR ARCH — thick arch on each temple anchored at y=10.
|
|
515
462
|
// The arch is widest at its crown (y=10, 4-5 px outward from the border)
|
|
516
463
|
// and tapers toward the ear base, creating a visible sideburn arc.
|
|
@@ -520,39 +467,31 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
520
467
|
const UPPER_ANCHOR_Y = HEAD_Y_MAX - 2; // y=10
|
|
521
468
|
const rngWisp2 = lcgRng(hashStr(`${seed}:${itemId}:outer-wisp2-${dirLabel}`));
|
|
522
469
|
const strandRows2 = palette.hairDepth >= 5 ? Math.min(palette.hairDepth - 4, 3) : 0;
|
|
523
|
-
|
|
524
470
|
if (strandRows2 > 0) {
|
|
525
471
|
const borderAtUpper = zones.border.filter(([, y]) => y === UPPER_ANCHOR_Y);
|
|
526
472
|
if (borderAtUpper.length >= 2) {
|
|
527
473
|
const hbL2 = Math.min(...borderAtUpper.map(([x]) => x));
|
|
528
474
|
const hbR2 = Math.max(...borderAtUpper.map(([x]) => x));
|
|
529
|
-
|
|
530
475
|
// Arch widths per row: narrow at crown (y=10), widest at ear base (y=12).
|
|
531
476
|
// Follows the head silhouette arc — wider where the head is wider.
|
|
532
477
|
const extBase = [2, 3, 4];
|
|
533
478
|
let lastLx2 = null,
|
|
534
479
|
lastRx2 = null;
|
|
535
|
-
|
|
536
480
|
for (let row = 0; row < strandRows2; row++) {
|
|
537
481
|
const y = UPPER_ANCHOR_Y + row; // y=10, y=11, y=12
|
|
538
482
|
if (y >= SKIN_GRID_DIM) break;
|
|
539
483
|
const ext2 = extBase[row] + (rngWisp2() < 0.4 ? 1 : 0); // 40 % +1 bonus
|
|
540
|
-
|
|
541
484
|
const innerL2 = hbL2 + 2; // 2 px toward center
|
|
542
485
|
const innerR2 = hbR2 - 2;
|
|
543
486
|
const lMin2 = Math.max(0, innerL2 - ext2);
|
|
544
487
|
const rMax2 = Math.min(SKIN_GRID_DIM - 1, innerR2 + ext2);
|
|
545
|
-
|
|
546
488
|
for (let x = lMin2; x < innerL2; x++) matrix[y][x] = hairIdx;
|
|
547
489
|
for (let x = innerR2 + 1; x <= rMax2; x++) matrix[y][x] = hairIdx;
|
|
548
|
-
|
|
549
490
|
if (lMin2 > 0 && matrix[y][lMin2 - 1] !== hairIdx) matrix[y][lMin2 - 1] = blackIdx;
|
|
550
491
|
if (rMax2 < SKIN_GRID_DIM - 1 && matrix[y][rMax2 + 1] !== hairIdx) matrix[y][rMax2 + 1] = blackIdx;
|
|
551
|
-
|
|
552
492
|
lastLx2 = lMin2;
|
|
553
493
|
lastRx2 = rMax2;
|
|
554
494
|
}
|
|
555
|
-
|
|
556
495
|
const tipY2 = UPPER_ANCHOR_Y + strandRows2; // first row after arch
|
|
557
496
|
if (tipY2 < SKIN_GRID_DIM) {
|
|
558
497
|
if (lastLx2 !== null && matrix[tipY2][Math.max(0, lastLx2)] !== hairIdx)
|
|
@@ -563,13 +502,12 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
563
502
|
}
|
|
564
503
|
}
|
|
565
504
|
}
|
|
566
|
-
|
|
567
505
|
// 2e. HAIR EDGE DISTORTIONS — 5–9 stray hair pixels scattered along the
|
|
568
506
|
// outer border of the hair zone for an organic, hand-drawn look.
|
|
569
507
|
// Each pixel sits one step outside the body silhouette and is closed
|
|
570
508
|
// with a black tip pixel for pixel-art definition.
|
|
571
509
|
{
|
|
572
|
-
const rngDist
|
|
510
|
+
const rngDist = lcgRng(hashStr(`${seed}:${itemId}:hair-distort-${dirLabel}`));
|
|
573
511
|
// Collect the outermost border column per hair row.
|
|
574
512
|
const borderInHair = zones.border.filter(([, y]) => y <= palette.hairDepth);
|
|
575
513
|
const bhrMap = new Map();
|
|
@@ -579,14 +517,14 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
579
517
|
b.max = Math.max(b.max, x);
|
|
580
518
|
bhrMap.set(y, b);
|
|
581
519
|
}
|
|
582
|
-
const bhrRows
|
|
520
|
+
const bhrRows = [...bhrMap.keys()];
|
|
583
521
|
const numDistort = 5 + Math.floor(rngDist() * 5); // 5–9
|
|
584
522
|
for (let i = 0; i < numDistort; i++) {
|
|
585
|
-
const y
|
|
523
|
+
const y = bhrRows[Math.floor(rngDist() * bhrRows.length)];
|
|
586
524
|
const bnd = bhrMap.get(y);
|
|
587
525
|
if (!bnd) continue;
|
|
588
526
|
const side = rngDist() < 0.5 ? -1 : 1;
|
|
589
|
-
const px
|
|
527
|
+
const px = side < 0 ? bnd.min - 1 : bnd.max + 1;
|
|
590
528
|
if (px >= 0 && px < SKIN_GRID_DIM && matrix[y][px] !== hairIdx) {
|
|
591
529
|
matrix[y][px] = hairIdx;
|
|
592
530
|
const bx = px + side;
|
|
@@ -594,38 +532,31 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
594
532
|
}
|
|
595
533
|
}
|
|
596
534
|
}
|
|
597
|
-
|
|
598
535
|
// 2f. MID-CROWN SIDE HAIR ARC — 7-row arc of hair strands following the
|
|
599
536
|
// head edge, centered at y=7 (crown shoulders). Right strand anchors
|
|
600
537
|
// near x=8 (character right), left mirrors it at x≈15. Width peaks
|
|
601
538
|
// at the center row (3 px outward) and tapers to 1 px at the extremes.
|
|
602
539
|
// Per-row 40 % width bonus + 30 % extra distortion pixel per side.
|
|
603
540
|
{
|
|
604
|
-
const CROWN_Y
|
|
605
|
-
const crownW
|
|
541
|
+
const CROWN_Y = 7;
|
|
542
|
+
const crownW = [3, 2, 2, 1]; // base width at |dy| = 0, 1, 2, 3
|
|
606
543
|
const rngCrown = lcgRng(hashStr(`${seed}:${itemId}:crown-side-${dirLabel}`));
|
|
607
|
-
|
|
608
544
|
for (let dy = -3; dy <= 3; dy++) {
|
|
609
545
|
const y = CROWN_Y + dy;
|
|
610
546
|
if (y < 0 || y >= SKIN_GRID_DIM) continue;
|
|
611
|
-
|
|
612
547
|
const borderAtY = zones.border.filter(([, by]) => by === y);
|
|
613
548
|
if (borderAtY.length < 2) continue;
|
|
614
549
|
const hbL = Math.min(...borderAtY.map(([x]) => x));
|
|
615
550
|
const hbR = Math.max(...borderAtY.map(([x]) => x));
|
|
616
|
-
|
|
617
551
|
const ext = crownW[Math.abs(dy)] + (rngCrown() < 0.4 ? 1 : 0);
|
|
618
|
-
|
|
619
552
|
// Left strand (character right, viewer left)
|
|
620
553
|
const lMin = Math.max(0, hbL - ext);
|
|
621
554
|
for (let x = lMin; x < hbL; x++) matrix[y][x] = hairIdx;
|
|
622
555
|
if (lMin > 0 && matrix[y][lMin - 1] !== hairIdx) matrix[y][lMin - 1] = blackIdx;
|
|
623
|
-
|
|
624
556
|
// Right strand (character left, viewer right)
|
|
625
557
|
const rMax = Math.min(SKIN_GRID_DIM - 1, hbR + ext);
|
|
626
558
|
for (let x = hbR + 1; x <= rMax; x++) matrix[y][x] = hairIdx;
|
|
627
559
|
if (rMax < SKIN_GRID_DIM - 1 && matrix[y][rMax + 1] !== hairIdx) matrix[y][rMax + 1] = blackIdx;
|
|
628
|
-
|
|
629
560
|
// Distortion: 30 % chance of one extra pixel per side
|
|
630
561
|
if (rngCrown() < 0.3) {
|
|
631
562
|
const px = lMin - 1;
|
|
@@ -644,16 +575,13 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
644
575
|
}
|
|
645
576
|
}
|
|
646
577
|
}
|
|
647
|
-
|
|
648
578
|
// 3. Clothes
|
|
649
579
|
paint(matrix, zones.shirt, shirtIdx);
|
|
650
580
|
paint(matrix, zones.pants, pantsIdx);
|
|
651
581
|
paint(matrix, zones.shoes, shoeIdx);
|
|
652
|
-
|
|
653
582
|
// 4. Black border outline — always last
|
|
654
583
|
const borderIdx = getOrAddColor(globalColors, [0, 0, 0, 255]);
|
|
655
584
|
paint(matrix, zones.border, borderIdx);
|
|
656
|
-
|
|
657
585
|
// 4b. CROWN BORDER SOFTENING — for non-shaved skins, replace most outer
|
|
658
586
|
// silhouette border pixels within the hair zone with hair colour so the
|
|
659
587
|
// crown blends into the background rather than having a hard black outline.
|
|
@@ -668,19 +596,23 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
668
596
|
for (let dx = -1; dx <= 1 && !isOuter; dx++) {
|
|
669
597
|
for (let dy = -1; dy <= 1 && !isOuter; dy++) {
|
|
670
598
|
if (dx === 0 && dy === 0) continue;
|
|
671
|
-
const nx = bx + dx,
|
|
672
|
-
|
|
673
|
-
|
|
599
|
+
const nx = bx + dx,
|
|
600
|
+
ny = by + dy;
|
|
601
|
+
if (
|
|
602
|
+
nx < 0 ||
|
|
603
|
+
nx >= SKIN_GRID_DIM ||
|
|
604
|
+
ny < 0 ||
|
|
605
|
+
ny >= SKIN_GRID_DIM ||
|
|
606
|
+
(!skinSet.has(`${nx},${ny}`) && !borderSet.has(`${nx},${ny}`))
|
|
607
|
+
)
|
|
674
608
|
isOuter = true;
|
|
675
609
|
}
|
|
676
610
|
}
|
|
677
611
|
if (isOuter && rngBorder() < 0.75) matrix[by][bx] = hairIdx;
|
|
678
612
|
}
|
|
679
613
|
}
|
|
680
|
-
|
|
681
614
|
return matrix;
|
|
682
615
|
}
|
|
683
|
-
|
|
684
616
|
/**
|
|
685
617
|
* Builds a 24 × 24 frame matrix for the UP (back-facing) direction.
|
|
686
618
|
*
|
|
@@ -701,27 +633,22 @@ function buildDirectionMatrix(zones, palette, globalColors, seed, itemId, dirLab
|
|
|
701
633
|
*/
|
|
702
634
|
function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
703
635
|
const matrix = Array.from({ length: SKIN_GRID_DIM }, () => Array(SKIN_GRID_DIM).fill(0));
|
|
704
|
-
|
|
705
636
|
const skinIdx = getOrAddColor(globalColors, palette.skinColor);
|
|
706
637
|
const hairIdx = getOrAddColor(globalColors, palette.hairColor);
|
|
707
638
|
const shirtIdx = getOrAddColor(globalColors, palette.shirtColor);
|
|
708
639
|
const pantsIdx = getOrAddColor(globalColors, palette.pantsColor);
|
|
709
640
|
const shoeIdx = getOrAddColor(globalColors, palette.shoeColor);
|
|
710
641
|
const blackIdx = getOrAddColor(globalColors, [0, 0, 0, 255]);
|
|
711
|
-
|
|
712
642
|
const rng = lcgRng(hashStr(`${seed}:${itemId}:up-hair`));
|
|
713
|
-
|
|
714
643
|
// All body pixels (skin + border) used for outer-silhouette detection
|
|
715
644
|
const allBodyCoords = [...zones.skin, ...zones.border];
|
|
716
645
|
const bodySet = new Set(allBodyCoords.map(([x, y]) => `${x},${y}`));
|
|
717
|
-
|
|
718
646
|
// 1. Paint full body → skin tone, then clothes, then border
|
|
719
647
|
paint(matrix, zones.skin, skinIdx);
|
|
720
648
|
paint(matrix, zones.shirt, shirtIdx);
|
|
721
649
|
paint(matrix, zones.pants, pantsIdx);
|
|
722
650
|
paint(matrix, zones.shoes, shoeIdx);
|
|
723
651
|
paint(matrix, zones.border, blackIdx);
|
|
724
|
-
|
|
725
652
|
// 1b. FACE FEATURE WIPE — the zones come from the DOWN (front-facing) template
|
|
726
653
|
// which embeds eyes, pupils and mouth as black border pixels. In the UP
|
|
727
654
|
// (back-of-head) view those pixels must not appear.
|
|
@@ -747,7 +674,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
747
674
|
}
|
|
748
675
|
if (!outer) matrix[by][bx] = skinIdx;
|
|
749
676
|
}
|
|
750
|
-
|
|
751
677
|
// 2. HEAD HAIR — two paths: shaved (hairDepth=0) or normal.
|
|
752
678
|
//
|
|
753
679
|
// SHAVED: scanline-fill the head with *skin tone* (covers inner face-feature
|
|
@@ -764,7 +690,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
764
690
|
b.max = Math.max(b.max, x);
|
|
765
691
|
headBounds.set(y, b);
|
|
766
692
|
}
|
|
767
|
-
|
|
768
693
|
// Shared helper: repaint outer head silhouette black; inner pixels stay as-is.
|
|
769
694
|
// A border pixel is "outer" if any 8-connected neighbour is outside bodySet.
|
|
770
695
|
const repaintOuterHeadBorder = () => {
|
|
@@ -783,7 +708,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
783
708
|
if (outer) matrix[by][bx] = blackIdx;
|
|
784
709
|
}
|
|
785
710
|
};
|
|
786
|
-
|
|
787
711
|
if (palette.hairDepth === 0) {
|
|
788
712
|
// ── SHAVED HEAD ─────────────────────────────────────────────────────────
|
|
789
713
|
// Fill head scanlines with skin tone (eliminates any face-feature artefacts)
|
|
@@ -798,10 +722,8 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
798
722
|
for (const [y, { min, max }] of headBounds) {
|
|
799
723
|
for (let x = min; x <= max; x++) matrix[y][x] = hairIdx;
|
|
800
724
|
}
|
|
801
|
-
|
|
802
725
|
// Repaint outer silhouette black; inner face-feature pixels stay as hair
|
|
803
726
|
repaintOuterHeadBorder();
|
|
804
|
-
|
|
805
727
|
// Crown border softening (UP) — same intent as 4b in buildDirectionMatrix.
|
|
806
728
|
// repaintOuterHeadBorder() just set outer head border pixels to black;
|
|
807
729
|
// randomly convert 75 % of them back to hair colour for a softer crown edge.
|
|
@@ -814,15 +736,15 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
814
736
|
for (let dx = -1; dx <= 1 && !isOuter; dx++) {
|
|
815
737
|
for (let dy = -1; dy <= 1 && !isOuter; dy++) {
|
|
816
738
|
if (dx === 0 && dy === 0) continue;
|
|
817
|
-
const nx = bx + dx,
|
|
818
|
-
|
|
819
|
-
|
|
739
|
+
const nx = bx + dx,
|
|
740
|
+
ny = by + dy;
|
|
741
|
+
if (nx < 0 || nx >= SKIN_GRID_DIM || ny < 0 || ny >= SKIN_GRID_DIM || !bodySet.has(`${nx},${ny}`))
|
|
742
|
+
isOuter = true;
|
|
820
743
|
}
|
|
821
744
|
}
|
|
822
745
|
if (isOuter && rngBorder() < 0.75) matrix[by][bx] = hairIdx;
|
|
823
746
|
}
|
|
824
747
|
}
|
|
825
|
-
|
|
826
748
|
// 2b side. SIDE HAIR STRANDS (UP) — identical geometry to the other three
|
|
827
749
|
// directions: 1-px strands just below HEAD_Y_MAX, anchored at the
|
|
828
750
|
// head border bounds at that row.
|
|
@@ -833,28 +755,21 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
833
755
|
const strandRows = Math.min(palette.hairDepth - 4, 3);
|
|
834
756
|
const hbL = headAtMax.min;
|
|
835
757
|
const hbR = headAtMax.max;
|
|
836
|
-
|
|
837
758
|
let lastLx = null,
|
|
838
759
|
lastRx = null;
|
|
839
|
-
|
|
840
760
|
for (let row = 0; row < strandRows; row++) {
|
|
841
761
|
const y = HEAD_Y_MAX + 1 + row;
|
|
842
762
|
if (y >= SKIN_GRID_DIM) break;
|
|
843
763
|
const ext = rngWisp() < 0.3 ? 2 : 1;
|
|
844
|
-
|
|
845
764
|
const lMin = Math.max(0, hbL - ext);
|
|
846
765
|
const rMax = Math.min(SKIN_GRID_DIM - 1, hbR + ext);
|
|
847
|
-
|
|
848
766
|
for (let x = lMin; x < hbL; x++) matrix[y][x] = hairIdx;
|
|
849
767
|
for (let x = hbR + 1; x <= rMax; x++) matrix[y][x] = hairIdx;
|
|
850
|
-
|
|
851
768
|
if (lMin > 0 && matrix[y][lMin - 1] !== hairIdx) matrix[y][lMin - 1] = blackIdx;
|
|
852
769
|
if (rMax < SKIN_GRID_DIM - 1 && matrix[y][rMax + 1] !== hairIdx) matrix[y][rMax + 1] = blackIdx;
|
|
853
|
-
|
|
854
770
|
lastLx = lMin;
|
|
855
771
|
lastRx = rMax;
|
|
856
772
|
}
|
|
857
|
-
|
|
858
773
|
const tipY = HEAD_Y_MAX + 1 + strandRows;
|
|
859
774
|
if (tipY < SKIN_GRID_DIM) {
|
|
860
775
|
if (lastLx !== null) matrix[tipY][Math.max(0, lastLx)] = blackIdx;
|
|
@@ -862,7 +777,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
862
777
|
}
|
|
863
778
|
}
|
|
864
779
|
}
|
|
865
|
-
|
|
866
780
|
// 2c side. UPPER SIDE HAIR ARCH (UP) — mirrors 2d in buildDirectionMatrix;
|
|
867
781
|
// thick arch starting at y=10, widest at the crown.
|
|
868
782
|
{
|
|
@@ -873,31 +787,24 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
873
787
|
const strandRows2 = Math.min(palette.hairDepth - 4, 3);
|
|
874
788
|
const hbL2 = headAtUpper.min;
|
|
875
789
|
const hbR2 = headAtUpper.max;
|
|
876
|
-
|
|
877
790
|
const extBase = [2, 3, 4];
|
|
878
791
|
let lastLx2 = null,
|
|
879
792
|
lastRx2 = null;
|
|
880
|
-
|
|
881
793
|
for (let row = 0; row < strandRows2; row++) {
|
|
882
794
|
const y = UPPER_ANCHOR_Y + row; // y=10, y=11, y=12
|
|
883
795
|
if (y >= SKIN_GRID_DIM) break;
|
|
884
796
|
const ext2 = extBase[row] + (rngWisp2() < 0.4 ? 1 : 0);
|
|
885
|
-
|
|
886
797
|
const innerL2 = hbL2 + 2; // 2 px toward center
|
|
887
798
|
const innerR2 = hbR2 - 2;
|
|
888
799
|
const lMin2 = Math.max(0, innerL2 - ext2);
|
|
889
800
|
const rMax2 = Math.min(SKIN_GRID_DIM - 1, innerR2 + ext2);
|
|
890
|
-
|
|
891
801
|
for (let x = lMin2; x < innerL2; x++) matrix[y][x] = hairIdx;
|
|
892
802
|
for (let x = innerR2 + 1; x <= rMax2; x++) matrix[y][x] = hairIdx;
|
|
893
|
-
|
|
894
803
|
if (lMin2 > 0 && matrix[y][lMin2 - 1] !== hairIdx) matrix[y][lMin2 - 1] = blackIdx;
|
|
895
804
|
if (rMax2 < SKIN_GRID_DIM - 1 && matrix[y][rMax2 + 1] !== hairIdx) matrix[y][rMax2 + 1] = blackIdx;
|
|
896
|
-
|
|
897
805
|
lastLx2 = lMin2;
|
|
898
806
|
lastRx2 = rMax2;
|
|
899
807
|
}
|
|
900
|
-
|
|
901
808
|
const tipY2 = UPPER_ANCHOR_Y + strandRows2;
|
|
902
809
|
if (tipY2 < SKIN_GRID_DIM) {
|
|
903
810
|
if (lastLx2 !== null && matrix[tipY2][Math.max(0, lastLx2)] !== hairIdx)
|
|
@@ -907,20 +814,19 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
907
814
|
}
|
|
908
815
|
}
|
|
909
816
|
}
|
|
910
|
-
|
|
911
817
|
// 2d. HEAD HAIR EDGE DISTORTIONS (UP) — 5–9 stray pixels along the outer
|
|
912
818
|
// head silhouette for organic texture. Identical approach to 2e in
|
|
913
819
|
// buildDirectionMatrix, using headBounds for column references.
|
|
914
820
|
{
|
|
915
|
-
const rngDist
|
|
916
|
-
const hbKeys
|
|
821
|
+
const rngDist = lcgRng(hashStr(`${seed}:${itemId}:hair-distort-up`));
|
|
822
|
+
const hbKeys = [...headBounds.keys()];
|
|
917
823
|
const numDistort = 5 + Math.floor(rngDist() * 5);
|
|
918
824
|
for (let i = 0; i < numDistort; i++) {
|
|
919
|
-
const y
|
|
825
|
+
const y = hbKeys[Math.floor(rngDist() * hbKeys.length)];
|
|
920
826
|
const bnd = headBounds.get(y);
|
|
921
827
|
if (!bnd) continue;
|
|
922
828
|
const side = rngDist() < 0.5 ? -1 : 1;
|
|
923
|
-
const px
|
|
829
|
+
const px = side < 0 ? bnd.min - 1 : bnd.max + 1;
|
|
924
830
|
if (px >= 0 && px < SKIN_GRID_DIM && matrix[y][px] !== hairIdx) {
|
|
925
831
|
matrix[y][px] = hairIdx;
|
|
926
832
|
const bx = px + side;
|
|
@@ -928,15 +834,13 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
928
834
|
}
|
|
929
835
|
}
|
|
930
836
|
}
|
|
931
|
-
|
|
932
837
|
// 2e. MID-CROWN SIDE HAIR ARC (UP) — mirrors 2f in buildDirectionMatrix;
|
|
933
838
|
// same 7-row arc centered at y=7, using headBounds for the silhouette
|
|
934
839
|
// edge reference instead of zones.border.
|
|
935
840
|
{
|
|
936
|
-
const CROWN_Y
|
|
937
|
-
const crownW
|
|
841
|
+
const CROWN_Y = 7;
|
|
842
|
+
const crownW = [3, 2, 2, 1];
|
|
938
843
|
const rngCrown = lcgRng(hashStr(`${seed}:${itemId}:crown-side-up`));
|
|
939
|
-
|
|
940
844
|
for (let dy = -3; dy <= 3; dy++) {
|
|
941
845
|
const y = CROWN_Y + dy;
|
|
942
846
|
if (y < 0 || y >= SKIN_GRID_DIM) continue;
|
|
@@ -944,17 +848,13 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
944
848
|
if (!bnd) continue;
|
|
945
849
|
const hbL = bnd.min;
|
|
946
850
|
const hbR = bnd.max;
|
|
947
|
-
|
|
948
851
|
const ext = crownW[Math.abs(dy)] + (rngCrown() < 0.4 ? 1 : 0);
|
|
949
|
-
|
|
950
852
|
const lMin = Math.max(0, hbL - ext);
|
|
951
853
|
for (let x = lMin; x < hbL; x++) matrix[y][x] = hairIdx;
|
|
952
854
|
if (lMin > 0 && matrix[y][lMin - 1] !== hairIdx) matrix[y][lMin - 1] = blackIdx;
|
|
953
|
-
|
|
954
855
|
const rMax = Math.min(SKIN_GRID_DIM - 1, hbR + ext);
|
|
955
856
|
for (let x = hbR + 1; x <= rMax; x++) matrix[y][x] = hairIdx;
|
|
956
857
|
if (rMax < SKIN_GRID_DIM - 1 && matrix[y][rMax + 1] !== hairIdx) matrix[y][rMax + 1] = blackIdx;
|
|
957
|
-
|
|
958
858
|
if (rngCrown() < 0.3) {
|
|
959
859
|
const px = lMin - 1;
|
|
960
860
|
if (px >= 0 && matrix[y][px] !== hairIdx) {
|
|
@@ -971,12 +871,10 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
971
871
|
}
|
|
972
872
|
}
|
|
973
873
|
}
|
|
974
|
-
|
|
975
874
|
// 4. EXTENDED HAIR — flows below the head onto the upper back.
|
|
976
875
|
// hairDepth (5–11) maps to hairExtend (2–8 rows below HEAD_Y_MAX).
|
|
977
876
|
const hairExtend = Math.min(palette.hairDepth - 3, 6); // 5→2 … 9→6, capped at 6
|
|
978
877
|
const extMaxY = Math.min(HEAD_Y_MAX + hairExtend, SKIN_GRID_DIM - 2);
|
|
979
|
-
|
|
980
878
|
// Compute body-bounds for each extension row (centering reference)
|
|
981
879
|
const extBounds = new Map();
|
|
982
880
|
for (const [x, y] of allBodyCoords) {
|
|
@@ -987,7 +885,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
987
885
|
extBounds.set(y, b);
|
|
988
886
|
}
|
|
989
887
|
const extYs = [...extBounds.keys()].sort((a, b) => a - b);
|
|
990
|
-
|
|
991
888
|
// Track actual hair strip bounds per row (for border painting)
|
|
992
889
|
const hairStripBounds = new Map();
|
|
993
890
|
for (const y of extYs) {
|
|
@@ -1010,7 +907,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
1010
907
|
max: Math.min(SKIN_GRID_DIM - 1, Math.ceil(cx) + halfW),
|
|
1011
908
|
});
|
|
1012
909
|
}
|
|
1013
|
-
|
|
1014
910
|
// 5. BLACK BORDERS on sides and bottom of the hair extension.
|
|
1015
911
|
for (const y of extYs) {
|
|
1016
912
|
const { min, max } = hairStripBounds.get(y);
|
|
@@ -1027,7 +923,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
1027
923
|
}
|
|
1028
924
|
}
|
|
1029
925
|
}
|
|
1030
|
-
|
|
1031
926
|
// 6. RANDOM WISPS — 2–4 stray hair pixels at edges for visual dynamism.
|
|
1032
927
|
// Each wisp extends 1–2 px beyond the current hair boundary,
|
|
1033
928
|
// with a black tip pixel closing it off.
|
|
@@ -1054,16 +949,13 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
1054
949
|
if (matrix[wy][tipX] !== hairIdx) matrix[wy][tipX] = blackIdx;
|
|
1055
950
|
}
|
|
1056
951
|
} // end else (normal hair)
|
|
1057
|
-
|
|
1058
952
|
return matrix;
|
|
1059
953
|
}
|
|
1060
|
-
|
|
1061
954
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1062
955
|
* WALK FRAME BUILDER
|
|
1063
956
|
* Two-frame walk cycle: frame 0 = idle pose, frame 1 = body shifted up 1px.
|
|
1064
957
|
* The vertical bob creates a natural walking bounce effect.
|
|
1065
958
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
1066
|
-
|
|
1067
959
|
/**
|
|
1068
960
|
* Derives a two-frame walk cycle from a pre-built idle matrix.
|
|
1069
961
|
* Frame 0: deep copy of idleMatrix.
|
|
@@ -1076,7 +968,6 @@ function buildUpDirectionMatrix(zones, palette, globalColors, seed, itemId) {
|
|
|
1076
968
|
function buildWalkFrames(idleMatrix) {
|
|
1077
969
|
const FOOT_TOP = SHOE_Y_MIN - 1; // y=22 — bottom of leg zone, just above shoes
|
|
1078
970
|
const midX = Math.floor(SKIN_GRID_DIM / 2); // x=12 — column boundary between left/right foot
|
|
1079
|
-
|
|
1080
971
|
// Build one walk frame: copy idle, then raise shoe row up 1 px for the chosen side,
|
|
1081
972
|
// leaving y=SHOE_Y_MIN transparent for that foot.
|
|
1082
973
|
const makeFrame = (liftLeft) => {
|
|
@@ -1089,15 +980,12 @@ function buildWalkFrames(idleMatrix) {
|
|
|
1089
980
|
}
|
|
1090
981
|
return frame;
|
|
1091
982
|
};
|
|
1092
|
-
|
|
1093
983
|
// frame0: right foot raised; frame1: left foot raised → alternating step cycle
|
|
1094
984
|
return [makeFrame(false), makeFrame(true)];
|
|
1095
985
|
}
|
|
1096
|
-
|
|
1097
986
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1098
987
|
* UUID HELPER (self-contained, no import from parent)
|
|
1099
988
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
1100
|
-
|
|
1101
989
|
/**
|
|
1102
990
|
* Derives a deterministic UUID v4 from an arbitrary seed string.
|
|
1103
991
|
* @param {string} seed
|
|
@@ -1110,12 +998,10 @@ function localSeedToUUIDv4(seed) {
|
|
|
1110
998
|
const hex = hash.toString('hex');
|
|
1111
999
|
return [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20, 32)].join('-');
|
|
1112
1000
|
}
|
|
1113
|
-
|
|
1114
1001
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1115
1002
|
* CUSTOM MULTI-FRAME GENERATOR
|
|
1116
1003
|
* Called by generateMultiFrame() when descriptor.customMultiFrameGenerator exists.
|
|
1117
1004
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
1118
|
-
|
|
1119
1005
|
/**
|
|
1120
1006
|
* Generates a complete MultiFrameResult for a skin item using template-based
|
|
1121
1007
|
* pixel painting. Each of the four cardinal directions gets a properly oriented
|
|
@@ -1127,15 +1013,12 @@ function localSeedToUUIDv4(seed) {
|
|
|
1127
1013
|
*/
|
|
1128
1014
|
function generateSkinMultiFrame(options, _descriptor) {
|
|
1129
1015
|
const { itemId, seed, frameCount = 1, startFrame = 0, frameDuration = 250 } = options;
|
|
1130
|
-
|
|
1131
1016
|
// Shared mutable palette; starts with index 0 = transparent
|
|
1132
1017
|
const globalColors = [[0, 0, 0, 0]];
|
|
1133
|
-
|
|
1134
1018
|
// Skin subtype is injected via the descriptor (set during registration).
|
|
1135
1019
|
// Falls back to 'random' for the legacy skin- prefix or any unknown descriptor.
|
|
1136
1020
|
const subtype = _descriptor?.skinSubtype ?? 'random';
|
|
1137
1021
|
const palette = deriveSkinPalette(seed, itemId, subtype);
|
|
1138
|
-
|
|
1139
1022
|
// Build idle direction matrices
|
|
1140
1023
|
const matrices = {
|
|
1141
1024
|
down: buildDirectionMatrix(ZONES.down, palette, globalColors, seed, itemId, 'down'),
|
|
@@ -1143,7 +1026,6 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1143
1026
|
left: buildDirectionMatrix(ZONES.left, palette, globalColors, seed, itemId, 'left'),
|
|
1144
1027
|
right: buildDirectionMatrix(ZONES.right, palette, globalColors, seed, itemId, 'right'),
|
|
1145
1028
|
};
|
|
1146
|
-
|
|
1147
1029
|
// Build 2-frame walk cycles from idle matrices (shared color palette, no extra allocations)
|
|
1148
1030
|
const walkFrames = {
|
|
1149
1031
|
down: buildWalkFrames(matrices.down),
|
|
@@ -1151,16 +1033,12 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1151
1033
|
left: buildWalkFrames(matrices.left),
|
|
1152
1034
|
right: buildWalkFrames(matrices.right),
|
|
1153
1035
|
};
|
|
1154
|
-
|
|
1155
1036
|
// Idle: frameCount identical copies of the direction matrix.
|
|
1156
1037
|
const makeIdleArray = (matrix) => Array.from({ length: frameCount }, () => matrix.map((row) => [...row]));
|
|
1157
|
-
|
|
1158
1038
|
// Walking: always exactly 2 frames (walk cycle is independent of frameCount).
|
|
1159
1039
|
const makeWalkArray = (frames2) => frames2.map((m) => m.map((row) => [...row]));
|
|
1160
|
-
|
|
1161
1040
|
const objectLayerRenderFramesData = {
|
|
1162
1041
|
frame_duration: frameDuration,
|
|
1163
|
-
is_stateless: false,
|
|
1164
1042
|
frames: {
|
|
1165
1043
|
// DOWN (08) idle
|
|
1166
1044
|
down_idle: makeIdleArray(matrices.down),
|
|
@@ -1184,7 +1062,6 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1184
1062
|
},
|
|
1185
1063
|
colors: globalColors,
|
|
1186
1064
|
};
|
|
1187
|
-
|
|
1188
1065
|
const objectLayerData = {
|
|
1189
1066
|
data: {
|
|
1190
1067
|
item: {
|
|
@@ -1205,7 +1082,6 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1205
1082
|
seed: localSeedToUUIDv4(`${seed}:${itemId}`),
|
|
1206
1083
|
},
|
|
1207
1084
|
};
|
|
1208
|
-
|
|
1209
1085
|
// Build synthetic frames array for layer-summary logging in cyberia.js
|
|
1210
1086
|
const layerSummary = [
|
|
1211
1087
|
{ layerKey: 'skin', layerId: `${itemId}-skin`, keys: ZONES.down.skin.map(() => ({ type: 'template' })) },
|
|
@@ -1215,7 +1091,6 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1215
1091
|
{ layerKey: 'shoes', layerId: `${itemId}-shoes`, keys: ZONES.down.shoes.map(() => ({ type: 'template' })) },
|
|
1216
1092
|
{ layerKey: 'border', layerId: `${itemId}-border`, keys: ZONES.down.border.map(() => ({ type: 'template' })) },
|
|
1217
1093
|
];
|
|
1218
|
-
|
|
1219
1094
|
const frames = Array.from({ length: frameCount }, (_, fi) => ({
|
|
1220
1095
|
itemId,
|
|
1221
1096
|
seed,
|
|
@@ -1224,7 +1099,6 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1224
1099
|
compositeFrameMatrix: matrices.down,
|
|
1225
1100
|
compositeColors: globalColors,
|
|
1226
1101
|
}));
|
|
1227
|
-
|
|
1228
1102
|
return {
|
|
1229
1103
|
itemId,
|
|
1230
1104
|
seed,
|
|
@@ -1234,11 +1108,9 @@ function generateSkinMultiFrame(options, _descriptor) {
|
|
|
1234
1108
|
objectLayerData,
|
|
1235
1109
|
};
|
|
1236
1110
|
}
|
|
1237
|
-
|
|
1238
1111
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
1239
1112
|
* REGISTRATION
|
|
1240
1113
|
* ═══════════════════════════════════════════════════════════════════════════ */
|
|
1241
|
-
|
|
1242
1114
|
/**
|
|
1243
1115
|
* Registers all skin semantic descriptors.
|
|
1244
1116
|
* Uses dependency injection — no import from the parent module.
|
|
@@ -1267,7 +1139,6 @@ export function registerSkinSemantics(registerFn) {
|
|
|
1267
1139
|
{ prefix: 'skin-natural', subtype: 'natural', desc: 'Natural hair colours (brown, blond, grey…)' },
|
|
1268
1140
|
{ prefix: 'skin-shaved', subtype: 'shaved', desc: 'Shaved / bald head — no hair' },
|
|
1269
1141
|
];
|
|
1270
|
-
|
|
1271
1142
|
const sharedLayers = {
|
|
1272
1143
|
skin: { generator: 'template-zone' },
|
|
1273
1144
|
hair: { generator: 'template-zone' },
|
|
@@ -1276,7 +1147,6 @@ export function registerSkinSemantics(registerFn) {
|
|
|
1276
1147
|
shoes: { generator: 'template-zone' },
|
|
1277
1148
|
border: { generator: 'template-zone' },
|
|
1278
1149
|
};
|
|
1279
|
-
|
|
1280
1150
|
for (const { prefix, subtype, desc } of SUBTYPES) {
|
|
1281
1151
|
registerFn(prefix, {
|
|
1282
1152
|
semanticTags: ['character', 'body', 'humanoid'],
|