@bitmagic/cli 0.1.52-dev.1 → 0.1.52-dev.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -11
- package/dist/assets/add.d.ts +40 -0
- package/dist/assets/add.js +229 -0
- package/dist/assets/add.js.map +1 -0
- package/dist/assets/entry.d.ts +44 -0
- package/dist/assets/entry.js +71 -0
- package/dist/assets/entry.js.map +1 -0
- package/dist/assets/file-kind.d.ts +61 -0
- package/dist/assets/file-kind.js +195 -0
- package/dist/assets/file-kind.js.map +1 -0
- package/dist/assets/list.d.ts +27 -0
- package/dist/assets/list.js +50 -0
- package/dist/assets/list.js.map +1 -0
- package/dist/assets/remove.d.ts +50 -0
- package/dist/assets/remove.js +91 -0
- package/dist/assets/remove.js.map +1 -0
- package/dist/assets/voxelize-glb.d.ts +37 -0
- package/dist/assets/voxelize-glb.js +65 -0
- package/dist/assets/voxelize-glb.js.map +1 -0
- package/dist/assets/vxl-summary.d.ts +32 -0
- package/dist/assets/vxl-summary.js +118 -0
- package/dist/assets/vxl-summary.js.map +1 -0
- package/dist/bin.js +25 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +15 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/assets.d.ts +56 -0
- package/dist/commands/assets.js +328 -0
- package/dist/commands/assets.js.map +1 -0
- package/dist/commands/cover.js +2 -3
- package/dist/commands/cover.js.map +1 -1
- package/dist/commands/forge.d.ts +2 -9
- package/dist/commands/forge.js +2 -13
- package/dist/commands/forge.js.map +1 -1
- package/dist/commands/generate.d.ts +41 -9
- package/dist/commands/generate.js +234 -54
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/judge.d.ts +7 -7
- package/dist/commands/judge.js +53 -25
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/levels.d.ts +33 -0
- package/dist/commands/levels.js +241 -0
- package/dist/commands/levels.js.map +1 -0
- package/dist/commands/number-flag.d.ts +7 -0
- package/dist/commands/number-flag.js +38 -0
- package/dist/commands/number-flag.js.map +1 -0
- package/dist/commands/publish.d.ts +6 -1
- package/dist/commands/publish.js +42 -15
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/run-deps.d.ts +27 -0
- package/dist/commands/run-deps.js +43 -0
- package/dist/commands/run-deps.js.map +1 -0
- package/dist/commands/verify.d.ts +4 -0
- package/dist/commands/verify.js +94 -51
- package/dist/commands/verify.js.map +1 -1
- package/dist/editor/journal.d.ts +48 -1
- package/dist/editor/journal.js +44 -0
- package/dist/editor/journal.js.map +1 -1
- package/dist/editor/previews.d.ts +6 -0
- package/dist/editor/previews.js +16 -0
- package/dist/editor/previews.js.map +1 -1
- package/dist/editor/server.js +228 -53
- package/dist/editor/server.js.map +1 -1
- package/dist/editor/shell-page.js +221 -116
- package/dist/editor/shell-page.js.map +1 -1
- package/dist/editor/voxel-save.d.ts +47 -0
- package/dist/editor/voxel-save.js +49 -0
- package/dist/editor/voxel-save.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +16 -0
- package/dist/errors.js.map +1 -1
- package/dist/forge/apply-modifications.d.ts +18 -1
- package/dist/forge/apply-modifications.js +15 -32
- package/dist/forge/apply-modifications.js.map +1 -1
- package/dist/forge/browser-host.js +9 -4
- package/dist/forge/browser-host.js.map +1 -1
- package/dist/forge/voxelize-session.d.ts +61 -0
- package/dist/forge/voxelize-session.js +137 -0
- package/dist/forge/voxelize-session.js.map +1 -0
- package/dist/generate/model.d.ts +36 -0
- package/dist/generate/model.js +90 -0
- package/dist/generate/model.js.map +1 -0
- package/dist/generate/prop.d.ts +52 -4
- package/dist/generate/prop.js +169 -102
- package/dist/generate/prop.js.map +1 -1
- package/dist/generate/stream.d.ts +7 -0
- package/dist/generate/stream.js +2 -1
- package/dist/generate/stream.js.map +1 -1
- package/dist/generate/vehicle.js +5 -85
- package/dist/generate/vehicle.js.map +1 -1
- package/dist/http/signed-upload.d.ts +55 -0
- package/dist/http/signed-upload.js +168 -0
- package/dist/http/signed-upload.js.map +1 -0
- package/dist/judge/facts.js +72 -57
- package/dist/judge/facts.js.map +1 -1
- package/dist/judge/record.d.ts +3 -0
- package/dist/judge/record.js +3 -0
- package/dist/judge/record.js.map +1 -1
- package/dist/levels/registry.d.ts +88 -0
- package/dist/levels/registry.js +309 -0
- package/dist/levels/registry.js.map +1 -0
- package/dist/messages/check.d.ts +30 -0
- package/dist/messages/check.js +119 -0
- package/dist/messages/check.js.map +1 -0
- package/dist/messages/notice.d.ts +58 -0
- package/dist/messages/notice.js +54 -0
- package/dist/messages/notice.js.map +1 -0
- package/dist/messages/refresh.d.ts +19 -0
- package/dist/messages/refresh.js +42 -0
- package/dist/messages/refresh.js.map +1 -0
- package/dist/project/jobs.d.ts +1 -1
- package/dist/project/jobs.js.map +1 -1
- package/dist/project/platform-declaration.d.ts +51 -0
- package/dist/project/platform-declaration.js +81 -0
- package/dist/project/platform-declaration.js.map +1 -0
- package/dist/publish/client.d.ts +13 -0
- package/dist/publish/client.js +13 -34
- package/dist/publish/client.js.map +1 -1
- package/dist/publish/platform.d.ts +42 -0
- package/dist/publish/platform.js +58 -0
- package/dist/publish/platform.js.map +1 -0
- package/dist/scaffold/project-files.js +124 -22
- package/dist/scaffold/project-files.js.map +1 -1
- package/dist/verify/artifacts.d.ts +19 -0
- package/dist/verify/artifacts.js +34 -0
- package/dist/verify/artifacts.js.map +1 -0
- package/dist/verify/browser.d.ts +10 -0
- package/dist/verify/browser.js +79 -9
- package/dist/verify/browser.js.map +1 -1
- package/dist/verify/classify.d.ts +37 -1
- package/dist/verify/classify.js +66 -2
- package/dist/verify/classify.js.map +1 -1
- package/dist/verify/device.d.ts +43 -0
- package/dist/verify/device.js +59 -0
- package/dist/verify/device.js.map +1 -0
- package/dist/verify/iterate.d.ts +21 -2
- package/dist/verify/iterate.js +43 -36
- package/dist/verify/iterate.js.map +1 -1
- package/dist/verify/mobile-parity.d.ts +42 -0
- package/dist/verify/mobile-parity.js +84 -0
- package/dist/verify/mobile-parity.js.map +1 -0
- package/dist/verify/platform.d.ts +47 -0
- package/dist/verify/platform.js +89 -0
- package/dist/verify/platform.js.map +1 -0
- package/dist/verify/result.d.ts +90 -6
- package/dist/verify/result.js +89 -10
- package/dist/verify/result.js.map +1 -1
- package/dist/verify/url.d.ts +9 -1
- package/dist/verify/url.js +10 -2
- package/dist/verify/url.js.map +1 -1
- package/package.json +4 -4
|
@@ -633,12 +633,41 @@ from its artwork.
|
|
|
633
633
|
Read \`engine/agent-docs/coordinate-system.md\`. Gameplay faces **+Z** while cameras face **−Z**, and
|
|
634
634
|
the two yaw conventions differ. Guessing produces code that compiles and moves the wrong way.
|
|
635
635
|
|
|
636
|
+
## Every control must work on a phone
|
|
637
|
+
|
|
638
|
+
Published games are played on phones whichever platform they were built for, and touch controls
|
|
639
|
+
are configured in **code** — there is no panel for them.
|
|
640
|
+
|
|
641
|
+
- **Bind an action once, for both platforms.** \`playerController.registerCustomAction({ action,
|
|
642
|
+
desktop: { keys: ['KeyR'] }, mobile: { label: 'LOAD', behavior: 'tap' } })\` binds the desktop
|
|
643
|
+
key AND creates the mobile button in one call, and throws if you leave \`mobile\` out. To declare
|
|
644
|
+
a genre's buttons up front instead, return them from \`declareMobileActions()\`. Read both flags
|
|
645
|
+
the same way afterwards: \`this.keys.reload\`.
|
|
646
|
+
- **Never** \`desktopControls.registerKeyHandler()\` or a raw \`document.addEventListener('keydown',
|
|
647
|
+
…)\` for a gameplay action. The first strands phone players and the engine logs a
|
|
648
|
+
\`[mobile-parity]\` error on game start; the raw listener strands them **silently** — the parity
|
|
649
|
+
check cannot see a listener that never went through the engine.
|
|
650
|
+
- **\`bitmagic verify\` enforces this.** Every run reports the parity gaps, naming the key and the
|
|
651
|
+
system that registered it — a warning normally, and a **failure** on a game whose
|
|
652
|
+
\`primaryPlatform\` is \`mobile\`. \`bitmagic verify --platform mobile\` goes further and boots
|
|
653
|
+
the game as a phone: touch controls, phone viewport, the engine's real mobile branch.
|
|
654
|
+
- **Say which platform the game is for.** \`"primaryPlatform": "mobile"\` at the top level of
|
|
655
|
+
\`src/work/game.json\` is what bitmagic.ai catalogs it as, and \`bitmagic publish\` sends it —
|
|
656
|
+
editing that file is the only way to set it. It also decides what \`verify\` boots and \`judge\`
|
|
657
|
+
grades by default, so setting it is how a phone game gets checked as one. For a game that only
|
|
658
|
+
works one way up, add \`"mobileOrientation": "portrait"\` (or \`"landscape"\`) to
|
|
659
|
+
\`worldProfileData\` in \`src/work/world.json\`; published mobile builds lock to it, and the
|
|
660
|
+
mobile verify run turns the phone the same way.
|
|
661
|
+
|
|
662
|
+
Details — button slots, icons, the joystick, the parity check: \`engine/agent-docs/control-system.md\`.
|
|
663
|
+
|
|
636
664
|
## Checking your work
|
|
637
665
|
|
|
638
666
|
\`\`\`
|
|
639
667
|
bitmagic check # typecheck against the vendored engine
|
|
640
668
|
bitmagic reload # tell the open browser you have finished a round of edits
|
|
641
669
|
bitmagic verify # boot the game in a real browser and report what broke
|
|
670
|
+
bitmagic verify --platform mobile # boot it as a phone: touch controls, phone viewport
|
|
642
671
|
bitmagic judge # grade the verify screenshot against a quality rubric (paid, cheap)
|
|
643
672
|
\`\`\`
|
|
644
673
|
|
|
@@ -729,7 +758,8 @@ bitmagic publish # verify-gate, build if needed, and ship to bitmagic.ai
|
|
|
729
758
|
Follow this order; \`publish\` enforces it and refuses otherwise:
|
|
730
759
|
|
|
731
760
|
1. Run \`bitmagic verify\` and get it passing. It writes \`.bitmagic/verify/result.json\`,
|
|
732
|
-
\`.bitmagic/verify/screenshot.png\` and \`.bitmagic/verify/console.log
|
|
761
|
+
\`.bitmagic/verify/screenshot.png\` and \`.bitmagic/verify/console.log\` — or the
|
|
762
|
+
\`.mobile\`-suffixed names when the run was a phone one.
|
|
733
763
|
|
|
734
764
|
The publish thumbnail is \`.bitmagic/cover.webp\` when \`bitmagic cover\` has produced one, and
|
|
735
765
|
that screenshot otherwise. The output names which one it used.
|
|
@@ -793,13 +823,16 @@ are developing, when rebuilding is cheap, rather than being blocked at the momen
|
|
|
793
823
|
You can create game content from here — no web editor needed:
|
|
794
824
|
|
|
795
825
|
\`\`\`
|
|
796
|
-
bitmagic generate skybox
|
|
797
|
-
bitmagic generate
|
|
798
|
-
bitmagic generate
|
|
799
|
-
bitmagic generate
|
|
800
|
-
bitmagic generate
|
|
801
|
-
bitmagic generate
|
|
802
|
-
bitmagic generate
|
|
826
|
+
bitmagic generate skybox --description "a stormy alien sky with two moons"
|
|
827
|
+
bitmagic generate background --description "layered pine forest at dusk"
|
|
828
|
+
bitmagic generate block-type --name mossy_brick --description "damp green moss on red brick"
|
|
829
|
+
bitmagic generate sound --prompt "heavy wooden door slamming" [--duration 3] [--loop]
|
|
830
|
+
bitmagic generate image --name hero-portrait --prompt "..." [--width 512] [--height 512]
|
|
831
|
+
bitmagic generate character --name guard --prompt "..." [--apply-to-player]
|
|
832
|
+
bitmagic generate animation --family gait --name LimpStride --based-on walk [--dial limpSide=right]
|
|
833
|
+
bitmagic generate animation --spec clip.json # melee and oneShot clips: a spec file
|
|
834
|
+
bitmagic generate model --prompt "a mossy stone arch" [--height 3]
|
|
835
|
+
bitmagic generate vehicle --prompt "a rusty orange pickup" | --preset VAN
|
|
803
836
|
\`\`\`
|
|
804
837
|
|
|
805
838
|
Run \`bitmagic generate <type> --help\` for the full flags of any one.
|
|
@@ -819,10 +852,9 @@ Four things worth knowing before you use these:
|
|
|
819
852
|
4. **A running \`bitmagic dev\` picks the new asset up on its own.** It reloads the browser once
|
|
820
853
|
the write settles; \`bitmagic reload\` forces it if you want it now.
|
|
821
854
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
will fail to load it.
|
|
855
|
+
Three lanes make a 3D object, and the wrong one wastes a generation: \`generate model\` mints a NEW
|
|
856
|
+
asset, \`generate prop --asset <id>\` UPGRADES a placeholder, \`assets add <file>\` uploads your own.
|
|
857
|
+
Splats, video and \`.vox\`/\`.fbx\` still need the web editor; never reference an asset you did not make.
|
|
826
858
|
|
|
827
859
|
## Forging a whole level
|
|
828
860
|
|
|
@@ -831,7 +863,9 @@ bitmagic forge --prompt "a ruined desert temple with a central courtyard"
|
|
|
831
863
|
\`\`\`
|
|
832
864
|
|
|
833
865
|
This designs a scene, builds its geometry, bakes it to voxels and writes the finished level —
|
|
834
|
-
terrain, props, spawn points — into \`src/work/world.json\`.
|
|
866
|
+
terrain, props, spawn points — into \`src/work/world.json\`. \`bitmagic levels list | add | rename
|
|
867
|
+
| set-start | remove\` manages them afterwards (free, local); \`set-start\` is how you change which
|
|
868
|
+
level the game boots into, and it moves the legacy mirror fields with it. Add \`--city\`, \`--dungeon\`,
|
|
835
869
|
\`--platformer\` or \`--freeform\` (at most one) for a different kind of place; omit them all for
|
|
836
870
|
natural terrain.
|
|
837
871
|
|
|
@@ -949,6 +983,7 @@ the engine, already wired** — and hand-building a worse version of it is the m
|
|
|
949
983
|
| Save points, checkpoints, persistence | Persistence system | \`persistence-system.md\` |
|
|
950
984
|
| Multiple levels or arenas | \`levels[]\` registry + \`engine.loadLevel()\` | \`level-system.md\` |
|
|
951
985
|
| Voice input, speaking to NPCs | \`engine.enablePushToTalk()\` | \`voice-input.md\` |
|
|
986
|
+
| A key for a new action — shoot, reload, boost, throw | \`registerCustomAction()\` / \`declareMobileActions()\` — one call binds the desktop key AND the phone button | \`control-system.md\` |
|
|
952
987
|
| The default player and its animation set | Already there | — |
|
|
953
988
|
|
|
954
989
|
A built-in still covers the request when the human names a style ("a katana" → the built-in katana).
|
|
@@ -976,11 +1011,17 @@ from GAME-DESIGN.md").
|
|
|
976
1011
|
Camera mode. Is the player an embodied walking character? Terrain form. Anything the recipe fixes
|
|
977
1012
|
that I would otherwise have defaulted differently.
|
|
978
1013
|
|
|
1014
|
+
## Platform
|
|
1015
|
+
\`primaryPlatform\` in \`game.json\` — \`desktop\` or \`mobile\`. Plus \`worldProfileData.mobileOrientation\`
|
|
1016
|
+
in \`world.json\` when the game only works one way up. If \`mobile\`: every action needs a touch
|
|
1017
|
+
button, and \`bitmagic verify\` fails the run otherwise.
|
|
1018
|
+
|
|
979
1019
|
## Slice 1 — the smallest playable thing
|
|
980
1020
|
- Core verb: the ONE thing the player does
|
|
981
1021
|
- Win: fires, even if the number is wrong
|
|
982
1022
|
- Lose:
|
|
983
1023
|
- On-screen feedback: score / timer / health
|
|
1024
|
+
- Controls: every action as desktop key + phone button, bound in one call
|
|
984
1025
|
- Engine systems: one line each, naming the doc — \`engine/agent-docs/…\`
|
|
985
1026
|
- Assets to generate: name them. A skybox and the cover usually earn their place in slice 1.
|
|
986
1027
|
- Level: procedural baseline terrain, unless the terrain IS the mechanic
|
|
@@ -1026,8 +1067,9 @@ forge is backlog item 1, run once the loop is proven.
|
|
|
1026
1067
|
|
|
1027
1068
|
Short, concrete, and honest. Three parts:
|
|
1028
1069
|
|
|
1029
|
-
- **Playable now** — one sentence, plus the literal control. "You can drive the
|
|
1030
|
-
lap. It is already reloaded in your browser — W to accelerate, A/D to steer
|
|
1070
|
+
- **Playable now** — one sentence, plus the literal control on both platforms. "You can drive the
|
|
1071
|
+
kart and complete a lap. It is already reloaded in your browser — W to accelerate, A/D to steer,
|
|
1072
|
+
and the same on a phone from the joystick and the GAS button."
|
|
1031
1073
|
- **Not in yet, on purpose** — three to five items from the backlog, so they know it is a choice and
|
|
1032
1074
|
not a bug.
|
|
1033
1075
|
- **Next I would do** — two or three, numbered, in order. Then ask which.
|
|
@@ -1171,11 +1213,30 @@ These docs are shared with Bitmagic's hosted agent, so two things read oddly her
|
|
|
1171
1213
|
|---|---|
|
|
1172
1214
|
| \`configure_game\`, \`manage-achievements\`, \`manage-levels\`, \`spawnpoint upsert\` | edit \`src/work/world.json\` directly |
|
|
1173
1215
|
| \`node bin/voxel.mjs\` / \`bin/world-edit.mjs\` | edit \`src/work/world.json\` directly |
|
|
1174
|
-
| \`generate_character\`, \`design-vehicle\`, skybox/sound/image/animation generation | \`bitmagic generate <type>\` |
|
|
1216
|
+
| \`generate_character\`, \`design-vehicle\`, \`generateBlockTypeTool\`, \`backgroundImageGenerationTool\`, skybox/sound/image/animation generation | \`bitmagic generate <type>\` |
|
|
1175
1217
|
| \`runWorldForger\` / \`run-world-forger\` | \`bitmagic forge\` |
|
|
1176
1218
|
|
|
1177
1219
|
Everything else in the doc — the engine APIs, the world.json shape, the gotchas — still applies.
|
|
1178
1220
|
|
|
1221
|
+
## Controls: one API, both platforms
|
|
1222
|
+
|
|
1223
|
+
The one lookup worth doing before you write any input code, because getting it wrong compiles,
|
|
1224
|
+
runs, and ships a control no phone can reach.
|
|
1225
|
+
|
|
1226
|
+
- \`PlayerController.registerCustomAction({ action, desktop: { keys }, mobile: { label, behavior } })\`
|
|
1227
|
+
— binds the desktop key and creates the mobile button atomically. **Throws** without \`mobile\`.
|
|
1228
|
+
- \`declareMobileActions(): MobileActionSpec[]\` on the genre's Game class — the declarative form,
|
|
1229
|
+
run before game systems initialize. \`MobileActionSpec\` (\`engine/MobileActionSpec.js\`):
|
|
1230
|
+
\`action\`, \`desktopKeys\`, \`iconKey\`, \`label\`, \`behavior: 'tap' | 'continuous'\`, \`preferredSlot\`.
|
|
1231
|
+
- Both write \`playerController.keys.<action>\`, so the gameplay code reads one flag either way.
|
|
1232
|
+
- \`playerController.verifyMobileParity()\` runs at game start and reports gaps as a
|
|
1233
|
+
\`[mobile-parity]\` console error. It only sees keys registered through the engine — a raw
|
|
1234
|
+
\`document.addEventListener('keydown', …)\` is invisible to it, which is why that pattern and
|
|
1235
|
+
\`desktopControls.registerKeyHandler()\` are both banned for gameplay actions.
|
|
1236
|
+
|
|
1237
|
+
Full reference, including the nine button slots and the built-in icon keys:
|
|
1238
|
+
\`engine/agent-docs/control-system.md\`.
|
|
1239
|
+
|
|
1179
1240
|
## The two rules that keep this cheap
|
|
1180
1241
|
|
|
1181
1242
|
**Stop searching, start writing.** Do not pre-verify every API you plan to touch. Once you know
|
|
@@ -1203,20 +1264,23 @@ you work. Re-read it before editing it.
|
|
|
1203
1264
|
export function renderGenerateSkill() {
|
|
1204
1265
|
return `---
|
|
1205
1266
|
name: generating-assets
|
|
1206
|
-
description: Use when this Bitmagic game needs a new skybox, sound effect, image, character, animation or drivable vehicle — anything that would otherwise require the web editor.
|
|
1267
|
+
description: Use when this Bitmagic game needs a new skybox, 2D background, custom block type, sound effect, image, character, animation or drivable vehicle, or when the creator has a model, sound, image or .vxl file of their own to add — anything that would otherwise require the web editor.
|
|
1207
1268
|
---
|
|
1208
1269
|
|
|
1209
1270
|
# Generating game assets
|
|
1210
1271
|
|
|
1211
|
-
The \`bitmagic\` CLI generates content directly into this project.
|
|
1272
|
+
The \`bitmagic\` CLI generates content directly into this project. Nine types work today:
|
|
1212
1273
|
|
|
1213
1274
|
| Command | Makes | Lands in |
|
|
1214
1275
|
|---|---|---|
|
|
1215
1276
|
| \`bitmagic generate skybox --description "..."\` | a 360° environment | \`worldProfileData.skyboxUrl\` |
|
|
1277
|
+
| \`bitmagic generate background --description "..."\` | a flat 2D backdrop | \`worldProfileData.skyboxUrl\` |
|
|
1278
|
+
| \`bitmagic generate block-type --name <id> --description "..."\` | a custom voxel block | \`worldProfileData.customBlockTypes[]\` |
|
|
1216
1279
|
| \`bitmagic generate sound --prompt "..."\` | a sound effect | \`assets[]\` |
|
|
1217
1280
|
| \`bitmagic generate image --name <id> --prompt "..."\` | a 2D image | \`assets[]\` |
|
|
1218
1281
|
| \`bitmagic generate character --name <id> --prompt "..."\` | a rigged character | \`assets[]\` |
|
|
1219
1282
|
| \`bitmagic generate animation --family gait ...\` or \`--spec <file>\` | an animation clip | \`assets[]\` |
|
|
1283
|
+
| \`bitmagic generate model --prompt "..."\` | a new 3D model | \`assets[]\` |
|
|
1220
1284
|
| \`bitmagic generate vehicle --prompt "..."\` | a drivable vehicle | \`assets[]\` |
|
|
1221
1285
|
|
|
1222
1286
|
\`--help\` on any subcommand lists its full flags.
|
|
@@ -1226,6 +1290,13 @@ spec in one of three families — \`gait\` (the only one with a flag form), \`me
|
|
|
1226
1290
|
(lists of key poses, so they come from a \`--spec\` file). It is also the cheapest, one spark a
|
|
1227
1291
|
clip, because it is solved locally rather than generated by a model.
|
|
1228
1292
|
|
|
1293
|
+
\`model\` is the one to reach for when the game needs an object that does not exist yet — a new
|
|
1294
|
+
asset from a sentence. It needs Chrome and a free port between 3000 and 3199, because only the
|
|
1295
|
+
engine can voxelize. \`--height\` scales it (interiors fill solid below 6 m and stay hollow above,
|
|
1296
|
+
so a building is not baked as a solid block), and \`--name\` sets what game code places by. Do not
|
|
1297
|
+
confuse it with \`prop\`, which REPLACES an existing placeholder and changes every instance already
|
|
1298
|
+
placed.
|
|
1299
|
+
|
|
1229
1300
|
\`vehicle\` is the one that needs more than a prompt to be useful. It creates a NEW asset (it does
|
|
1230
1301
|
not upgrade an existing one), it needs Chrome and a free port between 3000 and 3199 because only
|
|
1231
1302
|
the engine can voxelize, and \`--preset VAN\` builds a standard vehicle without calling the designer
|
|
@@ -1238,6 +1309,21 @@ engine.getVehicleSpawner().spawnAndEnterFromAsset({ x: 0, z: 0 }, '<assetName>',
|
|
|
1238
1309
|
|
|
1239
1310
|
Headlights, taillights and beacons are built and lit automatically — never ask for them.
|
|
1240
1311
|
|
|
1312
|
+
\`background\` and \`skybox\` write the **same field**. Pick one: \`skybox\` for a 360° panorama a
|
|
1313
|
+
free-look camera can turn in, \`background\` for a flat layered backdrop behind a fixed sidescroller
|
|
1314
|
+
or top-down camera. The engine wraps either onto the sky sphere, so a flat backdrop reads as a
|
|
1315
|
+
distant painted horizon rather than a full-screen image — and running one after the other replaces
|
|
1316
|
+
the first, silently.
|
|
1317
|
+
|
|
1318
|
+
\`block-type\` registers a block **before** anything can use it: \`--name\` is what
|
|
1319
|
+
\`create-voxel-asset\`, \`groundConfig.surfaceBlockType\` and building code refer to, and an
|
|
1320
|
+
unrecognised name falls back to flat vertex colour instead of failing. Names are lowercase
|
|
1321
|
+
snake_case, are compared case-insensitively, and cannot shadow a built-in (\`wood\`, \`leaves\`,
|
|
1322
|
+
\`stone\`, \`grass\`, \`sand\`, \`dirt\`, \`ice\`, \`asphalt\`, \`water\`, \`lava\`, \`marble\`, or the
|
|
1323
|
+
aliases \`trunk\`, \`foliage\`, \`rock\`, \`road\`). Re-running with the same \`--name\` **replaces**
|
|
1324
|
+
the entry rather than adding a second one. \`--side-description\` gives the side faces their own
|
|
1325
|
+
texture and generates a second one.
|
|
1326
|
+
|
|
1241
1327
|
Cover art is the one generation that is **not** a \`generate\` subcommand:
|
|
1242
1328
|
|
|
1243
1329
|
| Command | Makes | Lands in |
|
|
@@ -1281,11 +1367,22 @@ just a worse game. Two things to settle first:
|
|
|
1281
1367
|
Every failure before the asset arrives leaves \`world.json\` unchanged, and the command says so
|
|
1282
1368
|
explicitly. You never need to guess whether a failed generation half-applied.
|
|
1283
1369
|
|
|
1370
|
+
## Adding a file the creator already has
|
|
1371
|
+
|
|
1372
|
+
Not a generation, but the same registry: \`bitmagic assets add <file>\` uploads a \`.glb\`
|
|
1373
|
+
(voxelized in a headless browser — Chrome and a free port in 3000–3199, like \`vehicle\`), a
|
|
1374
|
+
\`.vxl\`, an image (\`.png/.jpg/.webp\`), audio (\`.opus/.ogg/.m4a/.mp3\`) or a config \`.json\`
|
|
1375
|
+
into \`assets[]\` and prints the id. \`--keep-glb\` registers a \`.glb\` as a polygon mesh instead;
|
|
1376
|
+
\`--voxel-size\`, \`--height\` and \`--hollow\` tune the bake. It costs no sparks. \`bitmagic
|
|
1377
|
+
assets list\` shows what is there; \`bitmagic assets remove <id>\` takes one out of \`world.json\`
|
|
1378
|
+
(the file stays in storage) and refuses while something in \`world.json\` still references it.
|
|
1379
|
+
|
|
1284
1380
|
## What is not available
|
|
1285
1381
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1382
|
+
Gaussian splats, video, and the import formats \`assets add\` refuses (\`.gltf\`, \`.vox\`, \`.qb\`,
|
|
1383
|
+
\`.fbx\`, \`.spz\`, \`.ply\`, \`.sog\`, \`.mp4\`) still need the web editor's Assets tab. Do not
|
|
1384
|
+
hand-edit \`world.json\` to reference an asset you have neither generated nor added; the game will
|
|
1385
|
+
fail to load it.
|
|
1289
1386
|
`;
|
|
1290
1387
|
}
|
|
1291
1388
|
/**
|
|
@@ -1380,6 +1477,7 @@ description: Use after finishing a milestone — a mechanics-plan slice shipped,
|
|
|
1380
1477
|
bitmagic verify # first: captures the screenshot the judge grades (no --fast)
|
|
1381
1478
|
bitmagic judge # one vision-model call; prints a scorecard, exits 0
|
|
1382
1479
|
bitmagic judge --genre platformer # sharpen the rubric: platformer, racing, shooter, combat, driving, tower-defense
|
|
1480
|
+
bitmagic judge --platform mobile # grade the phone frame: touch-HUD legibility, thumb reach, portrait framing
|
|
1383
1481
|
bitmagic judge --min-score 7 # exit 4 when the overall score is below the bar
|
|
1384
1482
|
bitmagic judge --json # the scorecard as one JSON document on stdout
|
|
1385
1483
|
\`\`\`
|
|
@@ -1390,6 +1488,10 @@ model and prints back four dimension scores (visual-quality, readability, design
|
|
|
1390
1488
|
and at most three findings — each naming what to change, most severe first. The scorecard is also
|
|
1391
1489
|
written to \`.bitmagic/judge.json\`.
|
|
1392
1490
|
|
|
1491
|
+
A mobile-primary game is graded on its phone frame by default — the one
|
|
1492
|
+
\`bitmagic verify --platform mobile\` captured — with the rubric re-pointed at what a phone player
|
|
1493
|
+
sees. Pass \`--platform desktop\` to grade the other one.
|
|
1494
|
+
|
|
1393
1495
|
It is paid (sparks, roughly the cost of one small model call) and deliberate — which is why it is
|
|
1394
1496
|
a command you run, not something verify does for you.
|
|
1395
1497
|
|
|
@@ -1472,7 +1574,7 @@ are lost on the next upgrade.
|
|
|
1472
1574
|
|---|---|
|
|
1473
1575
|
| \`planning-a-game/\` | starting a game, a genre, or anything touching more than one gameplay feature — scoping the first playable slice and writing \`mechanics-plan.md\` |
|
|
1474
1576
|
| \`finding-engine-apis/\` | you need an engine API and do not know its name or signature, or \`bitmagic check\` cannot resolve a symbol |
|
|
1475
|
-
| \`generating-assets/\` | the game needs a skybox, sound, image, character, animation or vehicle |
|
|
1577
|
+
| \`generating-assets/\` | the game needs a skybox, a background, a custom block type, sound, image, character, animation or vehicle |
|
|
1476
1578
|
| \`checking-usage/\` | asked what sparks were spent on, how much Pro allowance is left, or whether there is enough left to keep generating |
|
|
1477
1579
|
| \`judging-quality/\` | a milestone is done and the game deserves an independent quality verdict — \`bitmagic judge\` |
|
|
1478
1580
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { VerifyPlatform } from './platform.js';
|
|
2
|
+
export interface VerifyArtifactPaths {
|
|
3
|
+
/** The publish thumbnail candidate on desktop. Not written by a `--fast` run. */
|
|
4
|
+
screenshotPath: string;
|
|
5
|
+
consolePath: string;
|
|
6
|
+
/** Where the automatic software-GL re-run's console goes, so the first run's evidence survives. */
|
|
7
|
+
retryConsolePath: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function verifyArtifactPaths(artifactDir: string, platform: VerifyPlatform): VerifyArtifactPaths;
|
|
10
|
+
export declare const VERIFY_PLATFORMS: VerifyPlatform[];
|
|
11
|
+
/**
|
|
12
|
+
* Every screenshot any platform could write.
|
|
13
|
+
*
|
|
14
|
+
* A run deletes the ones it is not going to produce, extending the rule `--fast` already follows:
|
|
15
|
+
* a stale screenshot beside a fresh `result.json` is worse than none, because publish would ship
|
|
16
|
+
* a picture of some earlier state of the game — and after this change "earlier state" includes
|
|
17
|
+
* "the other platform, from an older run".
|
|
18
|
+
*/
|
|
19
|
+
export declare function allVerifyScreenshotPaths(artifactDir: string): string[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a verify run writes, per platform.
|
|
3
|
+
*
|
|
4
|
+
* One place, because a `both` run writes two of everything and the desktop names are a contract
|
|
5
|
+
* with two other commands: `bitmagic publish` reads `screenshot.png` as its thumbnail fallback
|
|
6
|
+
* and `bitmagic judge` grades it. Desktop therefore keeps exactly the names it has always had,
|
|
7
|
+
* and mobile infixes `.mobile` before the extension.
|
|
8
|
+
*
|
|
9
|
+
* A per-platform CONSOLE log is not cosmetic. Without it a `both` run would overwrite the desktop
|
|
10
|
+
* console — by far the most useful artifact when something breaks — with the mobile one, and the
|
|
11
|
+
* run would look like it only ever booted a phone.
|
|
12
|
+
*/
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
export function verifyArtifactPaths(artifactDir, platform) {
|
|
15
|
+
const suffix = platform === 'mobile' ? '.mobile' : '';
|
|
16
|
+
return {
|
|
17
|
+
screenshotPath: path.join(artifactDir, `screenshot${suffix}.png`),
|
|
18
|
+
consolePath: path.join(artifactDir, `console${suffix}.log`),
|
|
19
|
+
retryConsolePath: path.join(artifactDir, `console.retry${suffix}.log`),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export const VERIFY_PLATFORMS = ['desktop', 'mobile'];
|
|
23
|
+
/**
|
|
24
|
+
* Every screenshot any platform could write.
|
|
25
|
+
*
|
|
26
|
+
* A run deletes the ones it is not going to produce, extending the rule `--fast` already follows:
|
|
27
|
+
* a stale screenshot beside a fresh `result.json` is worse than none, because publish would ship
|
|
28
|
+
* a picture of some earlier state of the game — and after this change "earlier state" includes
|
|
29
|
+
* "the other platform, from an older run".
|
|
30
|
+
*/
|
|
31
|
+
export function allVerifyScreenshotPaths(artifactDir) {
|
|
32
|
+
return VERIFY_PLATFORMS.map((platform) => verifyArtifactPaths(artifactDir, platform).screenshotPath);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=artifacts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../src/verify/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAW7B,MAAM,UAAU,mBAAmB,CACjC,WAAmB,EACnB,QAAwB;IAExB,MAAM,MAAM,GAAG,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACtD,OAAO;QACL,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,MAAM,MAAM,CAAC;QACjE,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,MAAM,MAAM,CAAC;QAC3D,gBAAgB,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,gBAAgB,MAAM,MAAM,CAAC;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAqB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AAExE;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,mBAAmB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,cAAc,CAAC,CAAC;AACvG,CAAC"}
|
package/dist/verify/browser.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BrowserLaunchPlan, type RendererChoice } from '../browser-gl.js';
|
|
2
2
|
import { type VerifyObservations } from './classify.js';
|
|
3
|
+
import type { VerifyDeviceOptions } from './device.js';
|
|
3
4
|
export interface StartInteractionOptions {
|
|
4
5
|
/** Selector for the engine Play button. Its label is localizable and per-game overridable, so
|
|
5
6
|
* never match by text. */
|
|
@@ -56,6 +57,15 @@ export interface HeadlessCheckOptions {
|
|
|
56
57
|
* `browserGlArgs(process.env)`. `--mute-audio` is always added here, not by callers.
|
|
57
58
|
*/
|
|
58
59
|
launch?: BrowserLaunchPlan;
|
|
60
|
+
/**
|
|
61
|
+
* Emulate a phone for this check — viewport, screen, deviceScaleFactor, `isMobile` and
|
|
62
|
+
* `hasTouch` from `verify/device.ts`. Absent means the desktop 1280x720 context, byte-identical
|
|
63
|
+
* to what every run did before this existed, which is what `publish`'s smoke check keeps using.
|
|
64
|
+
*
|
|
65
|
+
* Pair it with a `?platform=mobile` URL: this side makes the BROWSER a phone, that side makes
|
|
66
|
+
* the ENGINE take its mobile branch, and only both together verify what a player gets.
|
|
67
|
+
*/
|
|
68
|
+
device?: VerifyDeviceOptions;
|
|
59
69
|
/**
|
|
60
70
|
* Capture the screenshot and measure its pixels (default true). `bitmagic verify --fast`
|
|
61
71
|
* turns this off for the inner loop: `screenshotBytes`/`canvasFrame` come back null and
|
package/dist/verify/browser.js
CHANGED
|
@@ -3,6 +3,7 @@ import { chromium } from 'playwright-core';
|
|
|
3
3
|
import { browserGlArgs } from '../browser-gl.js';
|
|
4
4
|
import { CliError } from '../errors.js';
|
|
5
5
|
import { RESPAWN_LINE, } from './classify.js';
|
|
6
|
+
import { parseMobileParityGap } from './mobile-parity.js';
|
|
6
7
|
import { DEFAULT_SETTLE_CONFIG, SETTLE_POLL_MS, isFreefalling, settleVerdict, } from './settle.js';
|
|
7
8
|
import { deriveEventsObservation } from './events.js';
|
|
8
9
|
export const DEFAULT_START_INTERACTION = {
|
|
@@ -66,7 +67,8 @@ export class VerifyBrowserSession {
|
|
|
66
67
|
// browser phase throws (e.g. page.goto timing out) — artifacts are the point of a failing run.
|
|
67
68
|
const consoleLines = [];
|
|
68
69
|
const pageErrors = [];
|
|
69
|
-
const
|
|
70
|
+
const contextOptions = options.device ?? DESKTOP_CONTEXT_OPTIONS;
|
|
71
|
+
const context = await this.browser.newContext(contextOptions);
|
|
70
72
|
try {
|
|
71
73
|
const page = await context.newPage();
|
|
72
74
|
await armContextLossProbe(page);
|
|
@@ -82,7 +84,7 @@ export class VerifyBrowserSession {
|
|
|
82
84
|
// worth having. The console log (written in `finally` below) is the useful artifact here.
|
|
83
85
|
if (options.captureScreenshot !== false) {
|
|
84
86
|
try {
|
|
85
|
-
fs.writeFileSync(options.screenshotPath, await page.screenshot());
|
|
87
|
+
fs.writeFileSync(options.screenshotPath, await page.screenshot(SCREENSHOT_OPTIONS));
|
|
86
88
|
}
|
|
87
89
|
catch {
|
|
88
90
|
// Page may already be unusable (closed context, crashed renderer) — nothing to save.
|
|
@@ -93,7 +95,7 @@ export class VerifyBrowserSession {
|
|
|
93
95
|
}
|
|
94
96
|
const interaction = options.startInteraction === undefined
|
|
95
97
|
? null
|
|
96
|
-
: await performStartInteraction(page, consoleLines, options.startInteraction);
|
|
98
|
+
: await performStartInteraction(page, consoleLines, options.startInteraction, options.device?.hasTouch === true);
|
|
97
99
|
// Verify settles adaptively — a game that provably reached stable gameplay is observed
|
|
98
100
|
// after a few seconds instead of the full fixed window, with `settleMs` as the hard cap.
|
|
99
101
|
// The smoke check keeps the fixed wait: its game lives in an iframe this probe does not
|
|
@@ -142,7 +144,7 @@ export class VerifyBrowserSession {
|
|
|
142
144
|
let screenshotBytes = null;
|
|
143
145
|
let canvasFrame = null;
|
|
144
146
|
if (options.captureScreenshot !== false) {
|
|
145
|
-
const screenshotBuffer = await page.screenshot();
|
|
147
|
+
const screenshotBuffer = await page.screenshot(SCREENSHOT_OPTIONS);
|
|
146
148
|
fs.writeFileSync(options.screenshotPath, screenshotBuffer);
|
|
147
149
|
screenshotBytes = screenshotBuffer.length;
|
|
148
150
|
canvasFrame = canvas === null
|
|
@@ -176,6 +178,9 @@ export class VerifyBrowserSession {
|
|
|
176
178
|
menuVisibleAfterSettle: await isMenuVisible(page),
|
|
177
179
|
};
|
|
178
180
|
}
|
|
181
|
+
// Only meaningful on a run that emulated a phone: on desktop the engine never builds
|
|
182
|
+
// MobileControls at all, so zero affordances there is the correct state, not a finding.
|
|
183
|
+
const touchControls = options.device === undefined ? undefined : await countTouchAffordances(page);
|
|
179
184
|
return {
|
|
180
185
|
consoleLines,
|
|
181
186
|
pageErrors,
|
|
@@ -188,6 +193,13 @@ export class VerifyBrowserSession {
|
|
|
188
193
|
snapshot,
|
|
189
194
|
settle,
|
|
190
195
|
events,
|
|
196
|
+
touchControls,
|
|
197
|
+
viewport: contextOptions.viewport,
|
|
198
|
+
// From the console this run already captured — the engine logs the check on every game
|
|
199
|
+
// start, desktop included (see verify/mobile-parity.ts). Behind the same gate as the
|
|
200
|
+
// snapshot and event probes: the check only runs at game start, so a run that never
|
|
201
|
+
// starts one has observed nothing, and `publish`'s smoke check keeps its exact shape.
|
|
202
|
+
mobileParity: engineTarget === null ? undefined : parseMobileParityGap(consoleLines),
|
|
191
203
|
};
|
|
192
204
|
}
|
|
193
205
|
finally {
|
|
@@ -199,6 +211,19 @@ export class VerifyBrowserSession {
|
|
|
199
211
|
}
|
|
200
212
|
}
|
|
201
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Every capture, on every platform.
|
|
216
|
+
*
|
|
217
|
+
* `scale: 'css'` rather than Playwright's default `'device'` because a phone context runs at
|
|
218
|
+
* deviceScaleFactor 3, and `measureCanvasFrame` below samples the captured image using the
|
|
219
|
+
* canvas's CSS-pixel rect. At device scale those coordinates would be off by 3x and the
|
|
220
|
+
* black-frame check would measure the wrong region of the image entirely. CSS scale keeps that
|
|
221
|
+
* invariant true on both paths — at DSF 1 the two are identical, so the desktop artifact is
|
|
222
|
+
* unchanged — and keeps the mobile PNG small enough for `bitmagic judge`'s image limit.
|
|
223
|
+
*/
|
|
224
|
+
const SCREENSHOT_OPTIONS = { scale: 'css' };
|
|
225
|
+
/** The context every run used before device emulation existed, and still the default. */
|
|
226
|
+
const DESKTOP_CONTEXT_OPTIONS = { viewport: { width: 1280, height: 720 } };
|
|
202
227
|
/** Window counter the init script below increments on every rAF tick. */
|
|
203
228
|
const FRAME_COUNT_FLAG = '__bitmagicFrameCount';
|
|
204
229
|
/**
|
|
@@ -505,8 +530,10 @@ async function measureCanvasFrame(page, screenshot, rect) {
|
|
|
505
530
|
const image = new Image();
|
|
506
531
|
image.src = input.dataUrl;
|
|
507
532
|
await image.decode();
|
|
508
|
-
// The screenshot is a viewport capture at
|
|
509
|
-
// page's CSS pixels
|
|
533
|
+
// The screenshot is a viewport capture taken at CSS scale (`SCREENSHOT_OPTIONS`), so its
|
|
534
|
+
// pixels are the page's CSS pixels whatever the device pixel ratio — which is what lets
|
|
535
|
+
// this rect, read from getBoundingClientRect, index straight into the image on a phone
|
|
536
|
+
// context at deviceScaleFactor 3. Clamp in case the canvas overhangs the viewport.
|
|
510
537
|
const left = Math.max(0, input.left);
|
|
511
538
|
const top = Math.max(0, input.top);
|
|
512
539
|
const width = Math.min(input.width, image.naturalWidth - left);
|
|
@@ -563,7 +590,8 @@ function hasStartMarker(consoleLines, marker) {
|
|
|
563
590
|
return consoleLines.some((line) => line.includes(marker));
|
|
564
591
|
}
|
|
565
592
|
/**
|
|
566
|
-
* Start gameplay the way a player does: wait for the engine Play button and
|
|
593
|
+
* Start gameplay the way a player does: wait for the engine Play button and press it — a click on
|
|
594
|
+
* the desktop run, a tap on the mobile one (see `tap` below). The button
|
|
567
595
|
* only becomes visible once the world has finished loading, so "button appeared" doubles as a
|
|
568
596
|
* load-complete signal. Games without the button (auto-starting genres, custom start UIs) are
|
|
569
597
|
* accepted through the start marker instead.
|
|
@@ -572,7 +600,17 @@ function hasStartMarker(consoleLines, marker) {
|
|
|
572
600
|
* failing game still leaves its menu screenshot and console log as artifacts, and classification
|
|
573
601
|
* decides pass/fail from what is recorded here.
|
|
574
602
|
*/
|
|
575
|
-
async function performStartInteraction(page, consoleLines, options
|
|
603
|
+
async function performStartInteraction(page, consoleLines, options,
|
|
604
|
+
/**
|
|
605
|
+
* Press Play with a touch tap instead of a mouse click.
|
|
606
|
+
*
|
|
607
|
+
* Derived from the context's own `hasTouch` rather than passed as its own flag, so the
|
|
608
|
+
* mismatch is unrepresentable: `tap()` throws outright on a context without touch. The mobile
|
|
609
|
+
* run must press Play the way its player does — the StartScreen binds `touchend` as well as
|
|
610
|
+
* `click`, and mobile gameplay start reads the transient user activation a real gesture leaves
|
|
611
|
+
* behind before it asks for fullscreen.
|
|
612
|
+
*/
|
|
613
|
+
tap) {
|
|
576
614
|
const button = page.locator(options.playButtonSelector).first();
|
|
577
615
|
let playButtonSeen = false;
|
|
578
616
|
let playButtonClicked = false;
|
|
@@ -592,7 +630,7 @@ async function performStartInteraction(page, consoleLines, options) {
|
|
|
592
630
|
}
|
|
593
631
|
if (playButtonSeen) {
|
|
594
632
|
try {
|
|
595
|
-
await button.click({ timeout: 5_000 });
|
|
633
|
+
await (tap ? button.tap({ timeout: 5_000 }) : button.click({ timeout: 5_000 }));
|
|
596
634
|
playButtonClicked = true;
|
|
597
635
|
}
|
|
598
636
|
catch {
|
|
@@ -606,6 +644,38 @@ async function performStartInteraction(page, consoleLines, options) {
|
|
|
606
644
|
}
|
|
607
645
|
return { playButtonSeen, playButtonClicked };
|
|
608
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* How many touch affordances the engine's MobileControls actually put on screen.
|
|
649
|
+
*
|
|
650
|
+
* The direct evidence that the mobile branch ran: `MobileControls` builds its UI only when
|
|
651
|
+
* `isMobileRuntime()` is true, so a phone-emulated run that ends with none of these elements
|
|
652
|
+
* either never took the branch or has no touch bindings at all. Matched by the engine's own
|
|
653
|
+
* stable class names rather than by structure, because the joystick and the action buttons are
|
|
654
|
+
* plain divs the game may restyle.
|
|
655
|
+
*
|
|
656
|
+
* Null on any failure, under the same "not observed is never a failure" contract as every other
|
|
657
|
+
* probe here.
|
|
658
|
+
*/
|
|
659
|
+
async function countTouchAffordances(page) {
|
|
660
|
+
try {
|
|
661
|
+
return await page.evaluate(() => {
|
|
662
|
+
/* eslint-disable no-undef -- browser-side callback, see the canvas probe above. */
|
|
663
|
+
try {
|
|
664
|
+
return {
|
|
665
|
+
joysticks: document.querySelectorAll('.hud-mobile-joystick-outer').length,
|
|
666
|
+
buttons: document.querySelectorAll('.hud-mobile-button').length,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
/* eslint-enable no-undef */
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
609
679
|
/**
|
|
610
680
|
* Whether the engine's start menu overlay is still displayed. After a successful start the
|
|
611
681
|
* StartScreen fades out and sets `display: none`; a menu still up at the end of the run means
|