dsh-plugin-vrmpet3d 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +251 -2
  3. package/assets/models/BlueFatFish.vrm.br +0 -0
  4. package/assets/models/README.md +99 -0
  5. package/assets/models/SampleModel.vrm.br +0 -0
  6. package/assets/models/vrmpet3d-models.json +116 -0
  7. package/assets/motions/README.md +177 -0
  8. package/assets/motions/attention/wave hand 1.vrma.br +0 -0
  9. package/assets/motions/attention/wave hand 2.vrma.br +0 -0
  10. package/assets/motions/attention/wave hand 3.vrma.br +0 -0
  11. package/assets/motions/dance/dance1.vrma.br +0 -0
  12. package/assets/motions/dance/dance2.vrma.br +0 -0
  13. package/assets/motions/fail/fall down1.vrma.br +0 -0
  14. package/assets/motions/fail/fall down2.vrma.br +0 -0
  15. package/assets/motions/idle/hungry1.vrma.br +0 -0
  16. package/assets/motions/idle/hungry2.vrma.br +0 -0
  17. package/assets/motions/idle/idle lookaround1.vrma.br +0 -0
  18. package/assets/motions/idle/idle lookaround2.vrma.br +0 -0
  19. package/assets/motions/idle/idle stretch1.vrma.br +0 -0
  20. package/assets/motions/idle/idle stretch2.vrma.br +0 -0
  21. package/assets/motions/idle/idle stretch3.vrma.br +0 -0
  22. package/assets/motions/idle/idle stretch4.vrma.br +0 -0
  23. package/assets/motions/idle/idle.vrma.br +0 -0
  24. package/assets/motions/idle/idle2.vrma.br +0 -0
  25. package/assets/motions/idle/idle3.vrma.br +0 -0
  26. package/assets/motions/idle/idle4.vrma.br +0 -0
  27. package/assets/motions/idle/touch hair1.vrma.br +0 -0
  28. package/assets/motions/idle/touch hair2.vrma.br +0 -0
  29. package/assets/motions/offline/sleeping.vrma.br +0 -0
  30. package/assets/motions/success/jump 1.vrma.br +0 -0
  31. package/assets/motions/success/jump 2.vrma.br +0 -0
  32. package/assets/motions/success/jump 3.vrma.br +0 -0
  33. package/assets/motions/vrmpet3d-motions.json +283 -0
  34. package/assets/motions/working/dance1.vrma.br +0 -0
  35. package/cordis.patch.yml +7 -0
  36. package/desktop/main.js +1824 -0
  37. package/desktop/package.json +7 -0
  38. package/desktop/preload.js +70 -0
  39. package/desktop/probe-motion-picker.js +122 -0
  40. package/desktop/probe-parts-editor.js +247 -0
  41. package/lib/brotli.js +74 -0
  42. package/lib/client.core.js +7759 -0
  43. package/lib/client.js +5267 -0
  44. package/lib/client.menu.js +501 -0
  45. package/lib/client.scene.js +5128 -0
  46. package/lib/client.vendor.js +55286 -0
  47. package/lib/config-page.html +220 -0
  48. package/lib/config-page.js +5645 -0
  49. package/lib/core.js +7737 -0
  50. package/lib/desktop-page.html +131 -0
  51. package/lib/desktop-page.js +4063 -0
  52. package/lib/desktop.js +536 -0
  53. package/lib/env.js +20 -0
  54. package/lib/global-page.html +379 -0
  55. package/lib/index.js +4287 -0
  56. package/lib/menu.js +479 -0
  57. package/lib/model-picker.js +884 -0
  58. package/lib/pages.css +350 -0
  59. package/lib/picker-page.html +90 -0
  60. package/lib/picker-page.js +326 -0
  61. package/lib/placeholder.js +566 -0
  62. package/lib/scene.js +5106 -0
  63. package/lib/settings.js +259 -0
  64. package/lib/setup-page.html +259 -0
  65. package/lib/setup-page.js +1059 -0
  66. package/lib/vrm-info.js +926 -0
  67. package/lib/whale.js +191 -0
  68. package/package.json +55 -4
@@ -0,0 +1,177 @@
1
+ # assets/motions — the bundled motion pack
2
+
3
+ These `.vrma` clips are **authored by this project's operator** and ship with the plugin under the same MIT licence as
4
+ the rest of this repository: no third-party terms, no borrowed model data. Each file's own `asset.generator` names the
5
+ pipeline that produced it (`text-to-motion`).
6
+
7
+ They are real VRM animations — a GLB whose JSON chunk carries `VRMC_vrm_animation` 1.0 over 44 humanoid bones — so
8
+ `Scene.loadMotion` accepts them as they are.
9
+
10
+ ## The folders ARE the states (operator, 2026-10-12)
11
+
12
+ > 「把对应的动作直接解析到指定目录,这样在选择动作的时候就不会出现很长一串」
13
+
14
+ The pack is filed **one folder per state**, and that filing is not a convention — it is the mapping. A folder name
15
+ in `stateFolders` below decides which clips a state's box set in 通用设置 → 状态与动作 is offered, so 空闲 shows the
16
+ idle clips and 工作中 shows the working ones instead of one list of everything:
17
+
18
+ | folder | state | 状态名 |
19
+ |---|---|---|
20
+ | `idle/` | `resting` | 空闲 |
21
+ | `working/` | `working` | 工作中 |
22
+ | `attention/` | `waiting` | 等待确认 |
23
+ | `success/` | `done` | 任务完成 |
24
+ | `fail/` | `error` | 出错 |
25
+ | `offline/` | `offline` | 离线 |
26
+
27
+ **`dance/` is deliberately NOT in that table.** It is a holding folder for the operator's dance clips (see
28
+ 「Compressed clips」 below): the clips are recognised and catalogued, but they belong to **no state** until the
29
+ operator says which one, so nothing plays a dance behind their back. Giving the folder to a state is a one-line
30
+ edit here — `"resting": ["idle", "dance"]` — and needs no code change.
31
+
32
+ **This is strict**: a state's picker offers only its own folder's clips. To give a clip to a state, put the file in
33
+ that state's folder. A state whose folder is missing or empty says so in the panel rather than showing an empty box
34
+ set. A clip that was ticked before the folders existed is still DRAWN in its state, even from another folder,
35
+ because hiding a stored mapping would read as losing it.
36
+
37
+ Renaming a folder is a supported edit: change it here in `stateFolders` too. A state with **no** entry is offered the
38
+ **whole** catalogue, which is how every build before this block behaved.
39
+
40
+ ## Compressed clips (`.vrma.br`), and why brotli and not LZMA2
41
+
42
+ Dance clips are large — the operator's first one is **8,080,556 bytes** — so a dance is stored compressed as
43
+ `<name>.vrma.br` and the plugin **unpacks it into memory** on the way to the renderer. Nothing else changes: a
44
+ compressed clip is listed as the VRMA it is (the panel marks it 已压缩), and the bytes that reach `Scene.loadMotion`
45
+ are always a plain VRMA.
46
+
47
+ Measured on that clip: `8,080,556 -> 2,836,574 bytes (35.1%)`, byte-identical after unpacking.
48
+
49
+ **LZMA2 would be about 10% smaller and is NOT supported.** Node's `zlib` cannot decode LZMA — measured 2026-10-12:
50
+ `unzipSync` / `inflateSync` / `gunzipSync` all fail with `Z_DATA_ERROR` on a file the reference liblzma wrote — and
51
+ this plugin ships with no npm dependencies, so LZMA would mean maintaining a hand-written LZMA2 decoder. One was
52
+ written and **did not decode real binary content correctly** (raw chunks and simple streams round-tripped; a real
53
+ VRMA diverged on the matched-literal path), so it was never wired in and the operator had it **deleted** rather than
54
+ carry unfinished code: a decompressor that returns plausible garbage hands the renderer a malformed model, which is
55
+ worse than not supporting the format. Brotli is in Node and in every Chromium the pet runs in, and it costs about
56
+ 2.8 MB on an 8 MB clip against LZMA2's 2.4 MB.
57
+
58
+ To compress a clip (the tool uses the same settings the loader is tested against, and refuses to write an archive
59
+ that does not decompress back to the same bytes):
60
+
61
+ ```bash
62
+ node scripts/compress-motions.mjs assets/motions/dance # add --dry-run to preview, --quality 0-11 to tune
63
+ ```
64
+
65
+ **A compressed file REPLACES the uncompressed one beside it.** If both `x.vrma` and `x.vrma.br` exist in the same
66
+ folder, only the archive is catalogued — otherwise one animation would be offered twice, under two names, claiming
67
+ 8 MB. Delete the archive and the plain clip comes back with no other change.
68
+
69
+ ## The `E_*` channels, and the defect they were blamed for (RESOLVED 2026-10-12)
70
+
71
+ Every clip here carries one or two `E_*` channels (`E_neutral` / `E_relaxed` / `E_blink`, all `translation`). They are
72
+ **not on the VRM humanoid rig**, so `resetAvatarPose` → `humanoid.resetNormalizedPose()` in
73
+ [lib/scene.js](../../lib/scene.js) does not touch them, and on 2026-10-12 they were named as the cause of a measured
74
+ "the model keeps a leftover pose after a clip stops".
75
+
76
+ **That attribution was wrong.** It was disproven by running the probe on the repository's own all-humanoid test clips
77
+ in `build/fixtures/vrma/`: `vrmpet3d-wave.vrma` and `vrmpet3d-rest.vrma` contain **no `E_*` track at all** and failed the
78
+ same two checks, while `vrmpet3d-test.vrma` — which drives only hips and head, and **no upper arm** — passed all 61.
79
+ The real cause was in the plugin, not in these files: `core.aimDownQuaternion` returned a **non-unit axis** (its cross
80
+ product's length is `sin(angle)`), so the caller built a non-unit arm quaternion (`|q| = 0.999599`), and the probe's
81
+ `Quaternion.angleTo` — which does not normalise — read that length as a **4.59° pose residual**. The pose was always
82
+ correct; the measurement was not. Fixed in both places: the maths now returns a unit axis, and the probe compares
83
+ rotations rather than quaternion lengths.
84
+
85
+ **So no clip needs re-exporting for this**, and the whole-skeleton snapshot idea is not required for it either.
86
+ The tool reads a clip and a model, serves them to a real browser, and reports 61 checks:
87
+
88
+ ```bash
89
+ node scripts/verify-motion.mjs --model assets/models/SampleModel.vrm.br --motion assets/motions/idle/idle.vrma.br
90
+ ```
91
+
92
+ Both files are compressed (`.br`), because that is how the pack ships, and the tool unpacks them itself (`lib/brotli.js`) — so
93
+ checking a shipped clip needs no manual step.
94
+
95
+ > ⚠️ **Known broken on 2026-10-15, and not by compression.** On this machine the run ends with
96
+ > `page error: Uncaught SyntaxError: missing ) after argument list` before any check is reported. The inlined scripts
97
+ > (`lib/client.vendor.js`, `lib/core.js`, `lib/placeholder.js`, `lib/scene.js`) contain no stray `</script`, and the tool's own
98
+ > page body parses clean, so the fault is inside the harness that only this tool builds — none of the nine suites runs this file,
99
+ > which is why nothing else noticed. It is recorded here rather than silently dropped.
100
+
101
+ The three files named here before (`coolIdleGlance.vrma`, `idle.vrma` and `innocentStandIdle.vrma`) had already moved into `idle/`
102
+ and been packed, and the operator trimmed the first and the last from the pack on 2026-10-15.
103
+
104
+ That script is the tool to re-check any new clip with, and it is **not** wired into any suite, so nothing else runs it.
105
+ What it confirms for each file: the humanoid rig is driven, the rotations stay finite, the RAW skinned bone follows the
106
+ clip, and a clip that stops returns the model to its procedural pose and to its rest pose.
107
+
108
+ ## `vrmpet3d-motions.json` — this folder's own manifest
109
+
110
+ The file beside the clips is what the host reads once per directory, and it declares three things:
111
+
112
+ ```json
113
+ {
114
+ "clips": {
115
+ "idle/idle.vrma": { "loop": true },
116
+ "idle/hungry1.vrma": { "loop": false },
117
+ "attention/wave hand 1.vrma": { "loop": true },
118
+ "fail/fall down1.vrma": { "loop": false }
119
+ },
120
+ "stateFolders": {
121
+ "resting": ["idle"],
122
+ "working": ["working"],
123
+ "waiting": ["attention"],
124
+ "done": ["success"],
125
+ "error": ["fail"],
126
+ "offline": ["offline"]
127
+ },
128
+ "defaults": {
129
+ "resting": ["idle/hungry1.vrma", "idle/idle.vrma"],
130
+ "waiting": ["attention/wave hand 1.vrma"]
131
+ }
132
+ }
133
+ ```
134
+
135
+ (The real file lists every clip the operator has configured, and `defaults` is his own mapping — see below.)
136
+
137
+ * **`clips`** is the 循环 / 单次 mode of each clip. `loop: false` makes it a one-shot — a reaction, not a posture —
138
+ and the pack carries BOTH kinds: the idle postures loop, the hungry / stretch / look-around / touch-hair / fall-down
139
+ clips are one-shots. A clip named in no entry defaults to `true`, the behaviour every build had before the flag
140
+ existed. **Keys are the path relative to this folder**, so a clip in a subfolder is declared `idle/name.vrma`: a key
141
+ written without its folder silently matches nothing, and the flag would not apply. A compressed clip's key is its
142
+ **file** name, suffix and all (`dance/极乐净土.vrma.br`).
143
+
144
+ What the modes MEAN is the renderer's rule (operator, 2026-10-13), and it is one rule in one place
145
+ (`Scene.afterOneShotEnded`, [lib/scene.js](../../lib/scene.js)): when a reaction ends and the state has a looping
146
+ clip, the pet **chains into that loop** (drawn at random); when the state has no loop, the reaction **holds its
147
+ last frame**. A one-shot is never played as a loop. The mode is applied per PLAYBACK, so the same file can be a
148
+ posture in one state and a reaction in another, and a state's own preference wins over this block.
149
+ * **`stateFolders`** is the state → folder mapping that keeps each picker short (see the table at the top). A value
150
+ is a folder name **or a list of them**, so one state can be offered several folders
151
+ (`"resting": ["idle", "dance"]`); a bare string is accepted because that is what a person hand-writes, and it is
152
+ normalised to a one-item list. A path, an empty value and a duplicate are dropped.
153
+ * **`defaults`** is which clips a user who has configured NOTHING gets, per state:
154
+ `{ "resting": ["idle/idle.vrma"], "working": ["working/typing.vrma"] }`. Keys are state names the session machine
155
+ can reach (`resting` `working` `waiting` `done` `error` `offline` — NOT `sleeping`, which is an internal state
156
+ that folds into `resting`), and values are **file names relative to THIS folder**, never ids: an id is minted
157
+ per profile from the absolute path, so it does not travel, and this file does.
158
+
159
+ It ships the OPERATOR'S OWN mapping since 2026-10-13 (「状态与动作:把目前的插件内的配置作为最终默认配置」): the clips
160
+ his profile had ticked per state, translated from his profile's ids to file names. A fresh install therefore
161
+ draws real clips with nothing to configure, and a state the user has configured — including one they
162
+ deliberately cleared — still wins over a default here. `verify-model.mjs` (group 5b) asserts the invariant that
163
+ matters for a hand-edited block: every name it declares resolves to a clip that exists, so a typo cannot ship as
164
+ a state that quietly draws nothing. See `core.motionMapWithDefaults`
165
+ ([lib/core.js](../../lib/core.js)) for the three rules and why they are read-time only.
166
+
167
+ ## How the pack reaches a user
168
+
169
+ This folder is a **bundled motion source**, scanned first by `buildMotionCatalog`
170
+ ([lib/index.js](../../lib/index.js)) exactly as `assets/models` is for models, so the clips above are offered
171
+ in 通用设置 → 状态与动作 out of the box and are catalogued with `source: 'bundled'`. Nothing needs importing;
172
+ `assets` is in `package.json`'s `files[]`, so a packed install carries them. The suites that assert all of this
173
+ are [scripts/verify-model.mjs](../../scripts/verify-model.mjs) (group 5b) and, for the defaults rule,
174
+ [scripts/verify.mjs](../../scripts/verify.mjs) (the motion-map group).
175
+
176
+ `scripts/make-vrma-fixtures.mjs` is a different thing: it is this repository's own authoring script for the TEST clips
177
+ in `build/fixtures/vrma/`, not the source of this pack.
Binary file
@@ -0,0 +1,283 @@
1
+ {
2
+ "clips": {
3
+ "attention/wave hand 1.vrma.br": {
4
+ "loop": true
5
+ },
6
+ "attention/wave hand 2.vrma.br": {
7
+ "loop": true
8
+ },
9
+ "attention/wave hand 3.vrma.br": {
10
+ "loop": true
11
+ },
12
+ "dance/dance1.vrma.br": {
13
+ "loop": false
14
+ },
15
+ "dance/dance2.vrma.br": {
16
+ "loop": false
17
+ },
18
+ "fail/fall down1.vrma.br": {
19
+ "loop": false
20
+ },
21
+ "fail/fall down2.vrma.br": {
22
+ "loop": false
23
+ },
24
+ "idle/hungry1.vrma.br": {
25
+ "loop": false
26
+ },
27
+ "idle/hungry2.vrma.br": {
28
+ "loop": false
29
+ },
30
+ "idle/idle lookaround1.vrma.br": {
31
+ "loop": false
32
+ },
33
+ "idle/idle lookaround2.vrma.br": {
34
+ "loop": false
35
+ },
36
+ "idle/idle stretch1.vrma.br": {
37
+ "loop": false
38
+ },
39
+ "idle/idle stretch2.vrma.br": {
40
+ "loop": false
41
+ },
42
+ "idle/idle stretch3.vrma.br": {
43
+ "loop": false
44
+ },
45
+ "idle/idle stretch4.vrma.br": {
46
+ "loop": false
47
+ },
48
+ "idle/idle.vrma.br": {
49
+ "loop": true
50
+ },
51
+ "idle/idle2.vrma.br": {
52
+ "loop": true
53
+ },
54
+ "idle/idle3.vrma.br": {
55
+ "loop": true
56
+ },
57
+ "idle/idle4.vrma.br": {
58
+ "loop": true
59
+ },
60
+ "idle/touch hair1.vrma.br": {
61
+ "loop": false
62
+ },
63
+ "idle/touch hair2.vrma.br": {
64
+ "loop": false
65
+ },
66
+ "offline/sleeping.vrma.br": {
67
+ "loop": true
68
+ },
69
+ "success/jump 1.vrma.br": {
70
+ "loop": true
71
+ },
72
+ "success/jump 2.vrma.br": {
73
+ "loop": true
74
+ },
75
+ "success/jump 3.vrma.br": {
76
+ "loop": true
77
+ },
78
+ "working/dance1.vrma.br": {
79
+ "loop": true
80
+ }
81
+ },
82
+ "stateFolders": {
83
+ "resting": [
84
+ "idle"
85
+ ],
86
+ "working": [
87
+ "working"
88
+ ],
89
+ "waiting": [
90
+ "attention"
91
+ ],
92
+ "done": [
93
+ "success"
94
+ ],
95
+ "error": [
96
+ "fail"
97
+ ],
98
+ "offline": [
99
+ "offline"
100
+ ],
101
+ "dancing": [
102
+ "dance"
103
+ ]
104
+ },
105
+ "defaults": {
106
+ "resting": [
107
+ "idle/hungry1.vrma.br",
108
+ "idle/hungry2.vrma.br",
109
+ "idle/idle lookaround1.vrma.br",
110
+ "idle/idle lookaround2.vrma.br",
111
+ "idle/idle stretch1.vrma.br",
112
+ "idle/idle stretch2.vrma.br",
113
+ "idle/idle stretch3.vrma.br",
114
+ "idle/idle stretch4.vrma.br",
115
+ "idle/idle.vrma.br",
116
+ "idle/idle2.vrma.br",
117
+ "idle/idle3.vrma.br",
118
+ "idle/idle4.vrma.br",
119
+ "idle/touch hair1.vrma.br",
120
+ "idle/touch hair2.vrma.br"
121
+ ],
122
+ "working": [
123
+ "working/dance1.vrma.br"
124
+ ],
125
+ "waiting": [
126
+ "attention/wave hand 1.vrma.br",
127
+ "attention/wave hand 2.vrma.br",
128
+ "attention/wave hand 3.vrma.br"
129
+ ],
130
+ "done": [
131
+ "success/jump 1.vrma.br",
132
+ "success/jump 2.vrma.br",
133
+ "success/jump 3.vrma.br"
134
+ ],
135
+ "error": [
136
+ "fail/fall down1.vrma.br",
137
+ "fail/fall down2.vrma.br"
138
+ ],
139
+ "offline": [
140
+ "offline/sleeping.vrma.br"
141
+ ],
142
+ "dancing": [
143
+ "dance/dance1.vrma.br",
144
+ "dance/dance2.vrma.br"
145
+ ]
146
+ },
147
+ "template": {
148
+ "resting": {
149
+ "idle/hungry1.vrma.br": {
150
+ "loop": false,
151
+ "expression": "none",
152
+ "gaze": false
153
+ },
154
+ "idle/hungry2.vrma.br": {
155
+ "loop": false,
156
+ "expression": "none",
157
+ "gaze": false
158
+ },
159
+ "idle/idle lookaround1.vrma.br": {
160
+ "loop": false,
161
+ "expression": "none",
162
+ "gaze": false
163
+ },
164
+ "idle/idle lookaround2.vrma.br": {
165
+ "loop": false,
166
+ "expression": "none",
167
+ "gaze": false
168
+ },
169
+ "idle/idle stretch1.vrma.br": {
170
+ "loop": false,
171
+ "expression": "none",
172
+ "gaze": false
173
+ },
174
+ "idle/idle stretch2.vrma.br": {
175
+ "loop": false,
176
+ "expression": "none",
177
+ "gaze": false
178
+ },
179
+ "idle/idle stretch3.vrma.br": {
180
+ "loop": false,
181
+ "expression": "none",
182
+ "gaze": false
183
+ },
184
+ "idle/idle stretch4.vrma.br": {
185
+ "loop": false,
186
+ "expression": "none",
187
+ "gaze": false
188
+ },
189
+ "idle/idle.vrma.br": {
190
+ "expression": "none"
191
+ },
192
+ "idle/idle2.vrma.br": {
193
+ "expression": "none"
194
+ },
195
+ "idle/idle3.vrma.br": {
196
+ "expression": "none"
197
+ },
198
+ "idle/idle4.vrma.br": {
199
+ "expression": "none"
200
+ },
201
+ "idle/touch hair1.vrma.br": {
202
+ "loop": false,
203
+ "expression": "none",
204
+ "gaze": false
205
+ },
206
+ "idle/touch hair2.vrma.br": {
207
+ "loop": false,
208
+ "expression": "none",
209
+ "gaze": false
210
+ }
211
+ },
212
+ "waiting": {
213
+ "attention/wave hand 1.vrma.br": {
214
+ "loop": true,
215
+ "expression": "none",
216
+ "gaze": false
217
+ },
218
+ "attention/wave hand 2.vrma.br": {
219
+ "expression": "none",
220
+ "gaze": false
221
+ },
222
+ "attention/wave hand 3.vrma.br": {
223
+ "expression": "none",
224
+ "gaze": false
225
+ }
226
+ },
227
+ "done": {
228
+ "success/jump 1.vrma.br": {
229
+ "expression": "happy",
230
+ "gaze": false
231
+ },
232
+ "success/jump 2.vrma.br": {
233
+ "expression": "happy",
234
+ "gaze": false
235
+ },
236
+ "success/jump 3.vrma.br": {
237
+ "expression": "happy",
238
+ "gaze": false
239
+ }
240
+ },
241
+ "error": {
242
+ "fail/fall down1.vrma.br": {
243
+ "loop": false,
244
+ "expression": "sad",
245
+ "gaze": false
246
+ },
247
+ "fail/fall down2.vrma.br": {
248
+ "loop": false,
249
+ "expression": "sad",
250
+ "gaze": false
251
+ }
252
+ },
253
+ "offline": {
254
+ "offline/sleeping.vrma.br": {
255
+ "loop": true,
256
+ "expression": "blink",
257
+ "gaze": false
258
+ }
259
+ },
260
+ "landing": {
261
+ "fail/fall down1.vrma.br": {
262
+ "loop": false
263
+ }
264
+ },
265
+ "working": {
266
+ "working/dance1.vrma.br": {
267
+ "expression": "none",
268
+ "gaze": false,
269
+ "speed": 150
270
+ }
271
+ },
272
+ "dancing": {
273
+ "dance/dance1.vrma.br": {
274
+ "expression": "none",
275
+ "loop": false
276
+ },
277
+ "dance/dance2.vrma.br": {
278
+ "expression": "none",
279
+ "loop": false
280
+ }
281
+ }
282
+ }
283
+ }
@@ -0,0 +1,7 @@
1
+ # Add only our plugin row. This patch replaces no official DSH component.
2
+ #
3
+ # The row must be named exactly like the package, because the client module
4
+ # system resolves a plugin's client bundle from its Loader entry name.
5
+ - insert:
6
+ - id: vrmpet3d
7
+ name: dsh-plugin-vrmpet3d