@fps-games/vfx 0.1.1 → 0.3.3
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/CHANGELOG.md +115 -0
- package/README.md +41 -0
- package/dist/effects/coin-burst/index.js +6 -2
- package/dist/effects/comet-trail-flame/index.js +3 -1
- package/dist/effects/debris-smoke/index.js +1 -0
- package/dist/effects/electric-arc/index.js +1 -0
- package/dist/effects/energy-shield/index.js +1 -0
- package/dist/effects/explosion/index.js +3 -2
- package/dist/effects/hit-flash/index.js +12 -4
- package/dist/effects/laser-beam/index.js +14 -2
- package/dist/effects/lumber_vehicle_upgrade_aura/index.js +1 -0
- package/dist/effects/monster-hit/index.js +2 -1
- package/dist/effects/player-upgrade/index.js +5 -3
- package/dist/effects/pulse-laser-volley/index.js +14 -4
- package/dist/effects/thruster-flame/index.js +3 -1
- package/dist/effects/treasure-attention/index.d.ts +54 -0
- package/dist/effects/treasure-attention/index.js +535 -0
- package/dist/effects/ufo-thread-flame/index.js +1 -0
- package/dist/effects/vehicle-upgrade/index.js +4 -1
- package/dist/effects/weapon-trail/index.js +8 -3
- package/dist/effects/wood_crusher_sawdust/index.d.ts +4 -0
- package/dist/effects/wood_cutter_sawdust/index.d.ts +4 -0
- package/dist/index.js +85 -17
- package/dist/types.d.ts +80 -0
- package/index.json +19 -1
- package/package.json +9 -4
- package/packages/effects/treasure-attention/vfx-params.json +49 -0
- package/tooling/vfx-cli.mjs +8 -1
- package/docs/AUTHORING_EFFECTS.md +0 -297
- package/docs/VFX_LIBRARY_ARCHITECTURE_chatgpt.md +0 -1580
- package/docs/VFX_LIBRARY_EXECUTION_PLAN.md +0 -229
- package/docs/VFX_LIBRARY_PLAN.md +0 -546
- package/docs/vfx-ab/RESULTS.md +0 -35
- package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/after.png +0 -0
- package/docs/vfx-ab/lumber-order/vehicle-exhaust-smoke/before.png +0 -0
- package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/after.png +0 -0
- package/docs/vfx-ab/lumber-truck/vehicle-exhaust-smoke/before.png +0 -0
- package/docs/vfx-integration_chatgpt.md +0 -462
- package/docs/weapon-trail-visualization.html +0 -241
- package/packages/EffectPackageService.ts +0 -240
- package/packages/SceneVfxService.ts +0 -96
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/runtime/simpleMeshEffect.v1.js +0 -333
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.frag.glsl +0 -25
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/shaders/mesh-effect-preview.vert.glsl +0 -11
- package/packages/build.mesh.yellow_build_upgrade_indicator/assets/textures/effect_jianbian.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/effect.json +0 -927
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/contact-sheet.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.gif +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.mp4 +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/preview.webm +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/media/thumbnail.png +0 -0
- package/packages/build.mesh.yellow_build_upgrade_indicator/metadata/description.json +0 -197
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-effect.original.json +0 -895
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/03-normalized-vfx-to-babylon-report.json +0 -241
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-overrides.json +0 -542
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/04-preview-tuning-report.json +0 -69
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/asset-map.json +0 -125
- package/packages/build.mesh.yellow_build_upgrade_indicator/provenance/lineage.json +0 -44
- package/packages/build.particle.golden_upgrade_starburst/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.golden_upgrade_starburst/assets/textures/effect_guangdian.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/effect.json +0 -880
- package/packages/build.particle.golden_upgrade_starburst/media/contact-sheet.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.gif +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.mp4 +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/preview.webm +0 -0
- package/packages/build.particle.golden_upgrade_starburst/media/thumbnail.png +0 -0
- package/packages/build.particle.golden_upgrade_starburst/metadata/description.json +0 -161
- package/packages/build.particle.golden_upgrade_starburst/provenance/03-effect.original.json +0 -762
- package/packages/build.particle.golden_upgrade_starburst/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
- package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-overrides.json +0 -436
- package/packages/build.particle.golden_upgrade_starburst/provenance/04-preview-tuning-report.json +0 -40
- package/packages/build.particle.golden_upgrade_starburst/provenance/asset-map.json +0 -115
- package/packages/build.particle.golden_upgrade_starburst/provenance/lineage.json +0 -40
- package/packages/build.particle.orange_upgrade_transform_sparks/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.orange_upgrade_transform_sparks/assets/textures/b5b27ab1-e740-4398-b407-848fc2b2c897.png +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/effect.json +0 -887
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.gif +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.mp4 +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/preview.webm +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/media/thumbnail.png +0 -0
- package/packages/build.particle.orange_upgrade_transform_sparks/metadata/description.json +0 -179
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-effect.original.json +0 -859
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/03-normalized-vfx-to-babylon-report.json +0 -260
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-overrides.json +0 -478
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/04-preview-tuning-report.json +0 -40
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/asset-map.json +0 -114
- package/packages/build.particle.orange_upgrade_transform_sparks/provenance/lineage.json +0 -41
- package/packages/build.particle.wood_chip_dust_burst/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/build.particle.wood_chip_dust_burst/assets/textures/effect_yanwu4.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/assets/textures/suipian3.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/effect.json +0 -1400
- package/packages/build.particle.wood_chip_dust_burst/media/contact-sheet.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.gif +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.mp4 +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/preview.webm +0 -0
- package/packages/build.particle.wood_chip_dust_burst/media/thumbnail.png +0 -0
- package/packages/build.particle.wood_chip_dust_burst/metadata/description.json +0 -173
- package/packages/build.particle.wood_chip_dust_burst/provenance/03-effect.original.json +0 -1277
- package/packages/build.particle.wood_chip_dust_burst/provenance/03-normalized-vfx-to-babylon-report.json +0 -329
- package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-overrides.json +0 -749
- package/packages/build.particle.wood_chip_dust_burst/provenance/04-preview-tuning-report.json +0 -60
- package/packages/build.particle.wood_chip_dust_burst/provenance/asset-map.json +0 -146
- package/packages/build.particle.wood_chip_dust_burst/provenance/lineage.json +0 -69
- package/packages/effects/building-upgrade/effect.json +0 -6
- package/packages/effects/building-upgrade/index.ts +0 -387
- package/packages/effects/character-entrance/effect.json +0 -6
- package/packages/effects/character-entrance/index.ts +0 -308
- package/packages/effects/coin-burst/index.ts +0 -43
- package/packages/effects/comet-trail-flame/assets/textures/trail_5x4.webp +0 -0
- package/packages/effects/comet-trail-flame/index.ts +0 -106
- package/packages/effects/conveyor-upgrade/effect.json +0 -6
- package/packages/effects/conveyor-upgrade/index.ts +0 -401
- package/packages/effects/debris-smoke/assets/models/rock_debris.glb +0 -0
- package/packages/effects/debris-smoke/assets/textures/smoke22.webp +0 -0
- package/packages/effects/debris-smoke/index.ts +0 -101
- package/packages/effects/door_open_gold_flakes/assets/runtime/goldFlakes.v1.js +0 -397
- package/packages/effects/door_open_gold_flakes/door_open_gold_flakes.json +0 -498
- package/packages/effects/door_open_gold_flakes/index.ts +0 -11
- package/packages/effects/electric-arc/assets/textures/spark_4x4.webp +0 -0
- package/packages/effects/electric-arc/index.ts +0 -62
- package/packages/effects/energy-shield/effect.json +0 -7
- package/packages/effects/energy-shield/index.ts +0 -431
- package/packages/effects/explosion/assets/textures/baozha29.webp +0 -0
- package/packages/effects/explosion/assets/textures/glow01.webp +0 -0
- package/packages/effects/explosion/assets/textures/lizi01.webp +0 -0
- package/packages/effects/explosion/assets/textures/xulie_lizibaozha02_5x5.webp +0 -0
- package/packages/effects/explosion/index.ts +0 -114
- package/packages/effects/ground-tile-upgrade/effect.json +0 -6
- package/packages/effects/ground-tile-upgrade/index.ts +0 -410
- package/packages/effects/hit-flash/index.ts +0 -69
- package/packages/effects/laser-beam/assets/textures/laser_glow.webp +0 -0
- package/packages/effects/laser-beam/assets/textures/laser_noise.webp +0 -0
- package/packages/effects/laser-beam/index.ts +0 -6
- package/packages/effects/laser-beam-factory.ts +0 -135
- package/packages/effects/lumber-cart-suction/effect.json +0 -6
- package/packages/effects/lumber-cart-suction/index.ts +0 -399
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb +0 -0
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/.active +0 -1
- package/packages/effects/lumber-debris-scatter/assets/models/lumber_prop_model.glb.versions/1781589063890_upload.glb +0 -0
- package/packages/effects/lumber-debris-scatter/assets/runtime/solidParticleScatter.v1.js +0 -236
- package/packages/effects/lumber-debris-scatter/effect.json +0 -228
- package/packages/effects/lumber-debris-scatter/index.ts +0 -780
- package/packages/effects/lumber_vehicle_gear_spin/assets/runtime/gearSpin.v1.js +0 -202
- package/packages/effects/lumber_vehicle_gear_spin/index.ts +0 -11
- package/packages/effects/lumber_vehicle_gear_spin/lumber_vehicle_gear_spin.json +0 -338
- package/packages/effects/lumber_vehicle_upgrade_aura/assets/runtime/upgradeAura.v1.js +0 -353
- package/packages/effects/lumber_vehicle_upgrade_aura/index.ts +0 -11
- package/packages/effects/lumber_vehicle_upgrade_aura/lumber_vehicle_upgrade_aura.json +0 -365
- package/packages/effects/lumber_vehicle_wood_chips/assets/runtime/solidParticleScatter.v1.js +0 -310
- package/packages/effects/lumber_vehicle_wood_chips/index.ts +0 -11
- package/packages/effects/lumber_vehicle_wood_chips/lumber_vehicle_wood_chips.json +0 -335
- package/packages/effects/monster-hit/index.ts +0 -128
- package/packages/effects/player-upgrade/effect.json +0 -6
- package/packages/effects/player-upgrade/index.ts +0 -635
- package/packages/effects/pulse-laser-volley/assets/textures/bolt_guangshu.webp +0 -0
- package/packages/effects/pulse-laser-volley/index.ts +0 -204
- package/packages/effects/resource-collect-sparkles/effect.json +0 -6
- package/packages/effects/resource-collect-sparkles/index.ts +0 -254
- package/packages/effects/run-footstep-dust/assets/runtime/runFootstepDust.v1.js +0 -215
- package/packages/effects/run-footstep-dust/effect.json +0 -183
- package/packages/effects/run-footstep-dust/index.ts +0 -377
- package/packages/effects/space-shared.ts +0 -120
- package/packages/effects/thruster-flame/index.ts +0 -305
- package/packages/effects/tree-chop-dust/assets/textures/tree_chop_dust_atlas.png +0 -0
- package/packages/effects/tree-chop-dust/effect.json +0 -8
- package/packages/effects/tree-chop-dust/index.ts +0 -202
- package/packages/effects/tree-leaf-chop-burst/assets/runtime/particleEmitter.v1.js +0 -303
- package/packages/effects/tree-leaf-chop-burst/assets/textures/tree_leaf_chop_burst_leaf_atlas.png +0 -0
- package/packages/effects/tree-leaf-chop-burst/effect.json +0 -498
- package/packages/effects/tree-leaf-chop-burst/index.ts +0 -294
- package/packages/effects/tree_chop_dust_burst/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/tree_chop_dust_burst/assets/textures/tree_chop_dust_burst_dust.png +0 -0
- package/packages/effects/tree_chop_dust_burst/index.ts +0 -13
- package/packages/effects/tree_chop_dust_burst/tree_chop_dust_burst.json +0 -412
- package/packages/effects/ufo-thread-flame/assets/textures/flame_flow.webp +0 -0
- package/packages/effects/ufo-thread-flame/assets/textures/flame_spike.webp +0 -0
- package/packages/effects/ufo-thread-flame/index.ts +0 -152
- package/packages/effects/vehicle-exhaust-smoke/assets/textures/vehicle_exhaust_smoke_atlas.png +0 -0
- package/packages/effects/vehicle-exhaust-smoke/effect.json +0 -8
- package/packages/effects/vehicle-exhaust-smoke/index.ts +0 -298
- package/packages/effects/vehicle-upgrade/effect.json +0 -6
- package/packages/effects/vehicle-upgrade/index.ts +0 -401
- package/packages/effects/weapon-trail/assets/textures/trail_wedge.png +0 -0
- package/packages/effects/weapon-trail/index.ts +0 -313
- package/packages/effects/wood-crusher-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
- package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
- package/packages/effects/wood-crusher-sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
- package/packages/effects/wood-crusher-sawdust/index.ts +0 -43
- package/packages/effects/wood-crusher-sawdust/wood_crusher_sawdust.json +0 -592
- package/packages/effects/wood-cutter-sawdust/assets/runtime/particleEmitter.v1.js +0 -312
- package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
- package/packages/effects/wood-cutter-sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
- package/packages/effects/wood-cutter-sawdust/index.ts +0 -43
- package/packages/effects/wood-cutter-sawdust/wood_cutter_sawdust.json +0 -592
- package/packages/effects/wood-sawdust-effect-factory.ts +0 -194
- package/packages/effects/wood_crusher_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_dust.png +0 -0
- package/packages/effects/wood_crusher_sawdust/assets/textures/wood_crusher_sawdust_fiber.png +0 -0
- package/packages/effects/wood_crusher_sawdust/index.ts +0 -29
- package/packages/effects/wood_crusher_sawdust/wood_crusher_sawdust.json +0 -592
- package/packages/effects/wood_cutter_sawdust/assets/runtime/particleEmitter.v1.js +0 -318
- package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_chip.png +0 -0
- package/packages/effects/wood_cutter_sawdust/assets/textures/wood_cutter_sawdust_dust.png +0 -0
- package/packages/effects/wood_cutter_sawdust/index.ts +0 -29
- package/packages/effects/wood_cutter_sawdust/wood_cutter_sawdust.json +0 -592
- package/packages/effects/wood_peel_material_test/assets/runtime/woodPeelMaterialTest.v1.js +0 -196
- package/packages/effects/wood_peel_material_test/index.ts +0 -11
- package/packages/effects/wood_peel_material_test/wood_peel_material_test.json +0 -136
- package/packages/env.particle.forest_edge_fog/assets/runtime/particleEmitter.v1.js +0 -210
- package/packages/env.particle.forest_edge_fog/assets/textures/f12a23c4-b924-4322-a260-3d982428f1e8.png +0 -0
- package/packages/env.particle.forest_edge_fog/assets/textures/yuan.png +0 -0
- package/packages/env.particle.forest_edge_fog/effect.json +0 -854
- package/packages/env.particle.forest_edge_fog/media/preview.gif +0 -0
- package/packages/env.particle.forest_edge_fog/media/preview.mp4 +0 -0
- package/packages/env.particle.forest_edge_fog/media/preview.webm +0 -0
- package/packages/env.particle.forest_edge_fog/media/thumbnail.png +0 -0
- package/packages/env.particle.forest_edge_fog/metadata/description.json +0 -148
- package/packages/env.particle.forest_edge_fog/provenance/03-effect.original.json +0 -827
- package/packages/env.particle.forest_edge_fog/provenance/03-normalized-vfx-to-babylon-report.json +0 -196
- package/packages/env.particle.forest_edge_fog/provenance/04-preview-overrides.json +0 -432
- package/packages/env.particle.forest_edge_fog/provenance/04-preview-tuning-report.json +0 -28
- package/packages/env.particle.forest_edge_fog/provenance/asset-map.json +0 -124
- package/packages/env.particle.forest_edge_fog/provenance/lineage.json +0 -41
- package/packages/generation.ts +0 -14
- package/packages/index.ts +0 -14
- package/packages/legacy-particle-runtime.ts +0 -113
- package/packages/manifestRuntimePackage.ts +0 -519
- package/packages/registry.ts +0 -85
- package/packages/rendering.ts +0 -13
- package/packages/resource.particle.viscous_oil_suction/assets/runtime/top_disk_suction/particleEmitter.v1.js +0 -424
- package/packages/resource.particle.viscous_oil_suction/assets/textures/Glow ad.png +0 -0
- package/packages/resource.particle.viscous_oil_suction/effect.json +0 -1254
- package/packages/resource.particle.viscous_oil_suction/media/preview.gif +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/preview.mp4 +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/preview.webm +0 -0
- package/packages/resource.particle.viscous_oil_suction/media/thumbnail.png +0 -0
- package/packages/resource.particle.viscous_oil_suction/metadata/description.json +0 -122
- package/packages/resource.particle.viscous_oil_suction/provenance/03-effect.original.json +0 -1051
- package/packages/resource.particle.viscous_oil_suction/provenance/03-normalized-vfx-to-babylon-report.json +0 -200
- package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-overrides.json +0 -581
- package/packages/resource.particle.viscous_oil_suction/provenance/04-preview-tuning-report.json +0 -28
- package/packages/resource.particle.viscous_oil_suction/provenance/asset-map.json +0 -111
- package/packages/resource.particle.viscous_oil_suction/provenance/lineage.json +0 -45
- package/packages/types.ts +0 -107
- package/tooling/check-dist-runtime.mjs +0 -166
- package/tooling/generate-index.mjs +0 -55
- package/tooling/test-guardrails.mjs +0 -157
- package/tooling/validate-boundary.mjs +0 -92
- package/tooling/validate-effects.mjs +0 -304
- package/tooling/validate.mjs +0 -53
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": "vfx-asset-map/1.0",
|
|
3
|
-
"generatedAt": "2026-05-21T13:04:29.644Z",
|
|
4
|
-
"sourcePackage": "vfx-intermediate/cocos-babylon-packages/gas_town_oil_suction",
|
|
5
|
-
"policy": {
|
|
6
|
-
"runtimeAssetsRoot": "assets/",
|
|
7
|
-
"sourceAssetsRoot": "source/",
|
|
8
|
-
"note": "assets/ contains files loaded by Babylon runtime. source/cocos/assets/ preserves the original Cocos asset tree for traceability and reconversion."
|
|
9
|
-
},
|
|
10
|
-
"entries": [
|
|
11
|
-
{
|
|
12
|
-
"id": "glow_ad",
|
|
13
|
-
"kind": "texture",
|
|
14
|
-
"group": "textures",
|
|
15
|
-
"role": "babylon-runtime",
|
|
16
|
-
"runtimeUse": true,
|
|
17
|
-
"packagePath": "assets/textures/Glow ad.png",
|
|
18
|
-
"sourcePackagePath": "assets/textures/Glow ad.png",
|
|
19
|
-
"sourceArchivePath": "source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/Glow ad.png",
|
|
20
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/Glow ad.png",
|
|
21
|
-
"sourceId": "dd44b0a9-0998-4c24-ba2e-d6ac9d1bebb7@6c48a",
|
|
22
|
-
"usedBy": [],
|
|
23
|
-
"originalProjectPath": "assets/ArtResources/Effect/GasTown_OilSuction/Glow ad.png"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "asset",
|
|
27
|
-
"kind": "material",
|
|
28
|
-
"group": "materials",
|
|
29
|
-
"role": "source-evidence",
|
|
30
|
-
"runtimeUse": false,
|
|
31
|
-
"packagePath": "source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/石油.mtl",
|
|
32
|
-
"sourcePackagePath": "assets/materials/石油.mtl",
|
|
33
|
-
"sourceArchivePath": "source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/石油.mtl",
|
|
34
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/石油.mtl",
|
|
35
|
-
"sourceId": "4aabf2b0-1d20-4e13-9384-ae47eb8522aa",
|
|
36
|
-
"usedBy": [],
|
|
37
|
-
"originalProjectPath": "assets/ArtResources/Effect/GasTown_OilSuction/石油.mtl"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"id": "particle_emitter_oil_suction_v1",
|
|
41
|
-
"kind": "script",
|
|
42
|
-
"group": "scripts",
|
|
43
|
-
"role": "babylon-runtime",
|
|
44
|
-
"runtimeUse": true,
|
|
45
|
-
"packagePath": "assets/runtime/gas_town_oil_suction/particleEmitter.v1.js",
|
|
46
|
-
"sourcePackagePath": "assets/runtime/gas_town_oil_suction/particleEmitter.v1.js",
|
|
47
|
-
"sourceArchivePath": null,
|
|
48
|
-
"sourcePath": null,
|
|
49
|
-
"sourceId": null,
|
|
50
|
-
"usedBy": []
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"id": "02-normalized-vfx",
|
|
54
|
-
"kind": "raw",
|
|
55
|
-
"group": "raw",
|
|
56
|
-
"role": "source-evidence",
|
|
57
|
-
"runtimeUse": false,
|
|
58
|
-
"packagePath": "source/02-normalized-vfx.json",
|
|
59
|
-
"sourcePackagePath": "source/02-normalized-vfx.json",
|
|
60
|
-
"sourceArchivePath": null,
|
|
61
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/02-normalized-vfx.json",
|
|
62
|
-
"sourceId": null,
|
|
63
|
-
"usedBy": []
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
"id": "01-source-dump",
|
|
67
|
-
"kind": "raw",
|
|
68
|
-
"group": "raw",
|
|
69
|
-
"role": "source-evidence",
|
|
70
|
-
"runtimeUse": false,
|
|
71
|
-
"packagePath": "source/01-source-dump.json",
|
|
72
|
-
"sourcePackagePath": "source/01-source-dump.json",
|
|
73
|
-
"sourceArchivePath": null,
|
|
74
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/01-source-dump.json",
|
|
75
|
-
"sourceId": null,
|
|
76
|
-
"usedBy": []
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"id": "asset",
|
|
80
|
-
"kind": "raw",
|
|
81
|
-
"group": "raw",
|
|
82
|
-
"role": "source-evidence",
|
|
83
|
-
"runtimeUse": false,
|
|
84
|
-
"packagePath": "source/raw/吸石油.prefab",
|
|
85
|
-
"sourcePackagePath": "source/raw/吸石油.prefab",
|
|
86
|
-
"sourceArchivePath": "source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab",
|
|
87
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab",
|
|
88
|
-
"sourceId": "29954e7e-d137-4c8b-a730-4f0320cbece8",
|
|
89
|
-
"usedBy": [],
|
|
90
|
-
"originalProjectPath": "assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab"
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
"id": "asset.meta",
|
|
94
|
-
"kind": "raw",
|
|
95
|
-
"group": "raw",
|
|
96
|
-
"role": "source-evidence",
|
|
97
|
-
"runtimeUse": false,
|
|
98
|
-
"packagePath": "source/raw/吸石油.prefab.meta",
|
|
99
|
-
"sourcePackagePath": "source/raw/吸石油.prefab.meta",
|
|
100
|
-
"sourceArchivePath": "source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab.meta",
|
|
101
|
-
"sourcePath": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source/cocos/assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab.meta",
|
|
102
|
-
"sourceId": "29954e7e-d137-4c8b-a730-4f0320cbece8",
|
|
103
|
-
"usedBy": [],
|
|
104
|
-
"originalProjectPath": "assets/ArtResources/Effect/GasTown_OilSuction/吸石油.prefab.meta"
|
|
105
|
-
}
|
|
106
|
-
],
|
|
107
|
-
"gcsPrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction",
|
|
108
|
-
"sourceArchivePrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source",
|
|
109
|
-
"fullProvenancePrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/provenance",
|
|
110
|
-
"canonicalSource": "gcs"
|
|
111
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": "vfx-library-lineage/1.0",
|
|
3
|
-
"savedAt": "2026-05-21T13:04:29.665Z",
|
|
4
|
-
"sourcePackage": "vfx-intermediate/cocos-babylon-packages/gas_town_oil_suction",
|
|
5
|
-
"sourceEffect": "03-effect.json",
|
|
6
|
-
"libraryPackage": "packages/resource.particle.viscous_oil_suction",
|
|
7
|
-
"effectId": "resource.particle.viscous_oil_suction",
|
|
8
|
-
"media": {
|
|
9
|
-
"previewVideo": "media/preview.webm",
|
|
10
|
-
"previewWebm": "media/preview.webm",
|
|
11
|
-
"previewMp4": "media/preview.mp4",
|
|
12
|
-
"previewGif": "media/preview.gif",
|
|
13
|
-
"thumbnail": "media/thumbnail.png",
|
|
14
|
-
"gcsUri": null
|
|
15
|
-
},
|
|
16
|
-
"recording": {
|
|
17
|
-
"provider": "browser-mediarecorder",
|
|
18
|
-
"recordedAt": "2026-05-21T13:04:30.575Z",
|
|
19
|
-
"mimeType": "video/webm;codecs=vp9",
|
|
20
|
-
"durationSec": 10,
|
|
21
|
-
"fps": 30,
|
|
22
|
-
"media": {
|
|
23
|
-
"previewVideo": "media/preview.webm",
|
|
24
|
-
"previewWebm": "media/preview.webm",
|
|
25
|
-
"previewMp4": "media/preview.mp4",
|
|
26
|
-
"previewGif": "media/preview.gif",
|
|
27
|
-
"thumbnail": "media/thumbnail.png",
|
|
28
|
-
"gcsUri": null
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"description": {
|
|
32
|
-
"provider": "litellm",
|
|
33
|
-
"status": "described",
|
|
34
|
-
"model": "gemini-3.1-pro-preview",
|
|
35
|
-
"generatedAt": "2026-05-21T13:30:34.263Z"
|
|
36
|
-
},
|
|
37
|
-
"gcsPrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction",
|
|
38
|
-
"sourceArchivePrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/source",
|
|
39
|
-
"fullProvenancePrefix": "gs://fps-vfx-library/packages/resource.particle.viscous_oil_suction/provenance",
|
|
40
|
-
"previousIds": [
|
|
41
|
-
"world.particle.gas_town_oil_suction",
|
|
42
|
-
"world.particle.viscous_oil_suction"
|
|
43
|
-
],
|
|
44
|
-
"renamedAt": "2026-05-21T13:39:08.485Z"
|
|
45
|
-
}
|
package/packages/types.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import type { Scene } from '@babylonjs/core/scene';
|
|
2
|
-
import type { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
|
3
|
-
import type { TransformNode } from '@babylonjs/core/Meshes/transformNode';
|
|
4
|
-
|
|
5
|
-
export type VfxDebugAnchor = 'playerRight' | 'player';
|
|
6
|
-
|
|
7
|
-
export type VfxParamValue = number | string | VfxColorValue;
|
|
8
|
-
export type VfxParamValues = Record<string, VfxParamValue>;
|
|
9
|
-
|
|
10
|
-
export interface VfxColorValue {
|
|
11
|
-
r: number;
|
|
12
|
-
g: number;
|
|
13
|
-
b: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export type VfxParamKind =
|
|
17
|
-
| 'number'
|
|
18
|
-
| 'color'
|
|
19
|
-
| 'enum'
|
|
20
|
-
| 'opacity'
|
|
21
|
-
| 'lifetime'
|
|
22
|
-
| 'scale';
|
|
23
|
-
|
|
24
|
-
export interface VfxParamDefinitionBase {
|
|
25
|
-
key: string;
|
|
26
|
-
label: string;
|
|
27
|
-
kind: VfxParamKind;
|
|
28
|
-
description?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface VfxNumberParamDefinition extends VfxParamDefinitionBase {
|
|
32
|
-
kind: 'number' | 'opacity' | 'lifetime' | 'scale';
|
|
33
|
-
min?: number;
|
|
34
|
-
max?: number;
|
|
35
|
-
step?: number;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface VfxColorParamDefinition extends VfxParamDefinitionBase {
|
|
39
|
-
kind: 'color';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface VfxEnumParamOption {
|
|
43
|
-
value: string;
|
|
44
|
-
label: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface VfxEnumParamDefinition extends VfxParamDefinitionBase {
|
|
48
|
-
kind: 'enum';
|
|
49
|
-
options: VfxEnumParamOption[];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export type VfxParamDefinition = VfxNumberParamDefinition | VfxColorParamDefinition | VfxEnumParamDefinition;
|
|
53
|
-
|
|
54
|
-
export interface VfxEffectPackageContext<PParams extends VfxParamValues = VfxParamValues> {
|
|
55
|
-
scene: Scene;
|
|
56
|
-
root: TransformNode;
|
|
57
|
-
position: Vector3;
|
|
58
|
-
rotationY: number;
|
|
59
|
-
renderingGroupId?: number;
|
|
60
|
-
params: PParams;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface VfxEffectHandle {
|
|
64
|
-
root: TransformNode;
|
|
65
|
-
ready?: Promise<void>;
|
|
66
|
-
dispose(): void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface VfxEffectManifest {
|
|
70
|
-
id: string;
|
|
71
|
-
nameZh: string;
|
|
72
|
-
assets?: string[];
|
|
73
|
-
source?: string;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface VfxSpawnParamKeys {
|
|
77
|
-
offsetX?: string;
|
|
78
|
-
offsetY?: string;
|
|
79
|
-
offsetZ?: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface VfxSpawnTransform {
|
|
83
|
-
position?: Vector3;
|
|
84
|
-
rotationY?: number;
|
|
85
|
-
renderingGroupId?: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface VfxParamsFile {
|
|
89
|
-
schemaVersion: string;
|
|
90
|
-
effectId?: string;
|
|
91
|
-
updatedAt?: string | null;
|
|
92
|
-
params?: Partial<VfxParamValues>;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface VfxEffectPackage<PParams extends VfxParamValues = VfxParamValues> {
|
|
96
|
-
id: string;
|
|
97
|
-
nameZh: string;
|
|
98
|
-
manifest?: VfxEffectManifest;
|
|
99
|
-
defaultParams: PParams;
|
|
100
|
-
debugAnchor?: VfxDebugAnchor;
|
|
101
|
-
spawnParams?: VfxSpawnParamKeys;
|
|
102
|
-
savedParams?: Partial<VfxParamValues>;
|
|
103
|
-
debugParams: VfxParamDefinition[];
|
|
104
|
-
create(context: VfxEffectPackageContext<PParams>): VfxEffectHandle;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export type VfxDebugOverrides = Record<string, Partial<VfxParamValues>>;
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// 新增特效 guardrail · Layer 2(构建产物禁运行时扫描,见 issue #3,最强一层)。
|
|
3
|
-
// `pnpm build` 之后扫 dist/,镜像消费方 Playable bundle 策略(lumber-order
|
|
4
|
-
// scripts/check-playable-asset-bundle.mjs 的 forbiddenTokens),把"会坑消费方"的重运行时
|
|
5
|
-
// 提前到库侧拦住 —— 即便它是被某个特效"间接"拉进来的(顺着 dist 的 chunk import 图归因到特效)。
|
|
6
|
-
//
|
|
7
|
-
// 用法:
|
|
8
|
-
// node tooling/check-dist-runtime.mjs # 扫 dist/
|
|
9
|
-
// node tooling/check-dist-runtime.mjs --dist <dir> # 换扫描目录(回归 fixture 用)
|
|
10
|
-
// 退出码:命中 → 1;干净 → 0。
|
|
11
|
-
import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs';
|
|
12
|
-
import { resolve, dirname, join, relative } from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
-
|
|
15
|
-
const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
-
const argv = process.argv.slice(2);
|
|
17
|
-
const distArg = argv.indexOf('--dist') >= 0 ? argv[argv.indexOf('--dist') + 1] : undefined;
|
|
18
|
-
const DIST = resolve(distArg ?? join(LIB_ROOT, 'dist'));
|
|
19
|
-
|
|
20
|
-
// 精确 token —— 类名/扩展名/MIME,绝不用宽松子串(如 `basis` 会误报 RingBasis 变量)。
|
|
21
|
-
// 与 lumber-order check-playable-asset-bundle.mjs 的 forbiddenTokens 对齐 + 补 draco/ktx2。
|
|
22
|
-
const SUBSTRING_TOKENS = [
|
|
23
|
-
'EXT_meshopt_compression', 'MeshoptDecoder', 'meshopt_decoder',
|
|
24
|
-
'KHR_draco_mesh_compression', 'DracoCompression', 'DracoDecoder', 'draco_decoder', 'draco_wasm_wrapper',
|
|
25
|
-
'KHR_texture_basisu', 'KhronosTextureContainer2', '_KTX2Decoder', 'MSCTranscoder', 'basis_transcoder',
|
|
26
|
-
'application/x-glb-brotli', 'brotli-dec-wasm',
|
|
27
|
-
'application/wasm', 'data:application/wasm',
|
|
28
|
-
'WebAssembly.Module', 'WebAssembly.instantiate',
|
|
29
|
-
];
|
|
30
|
-
// `.wasm` 资源 URL:要求路径分隔符(/),只拦真资源路径如 "./decoder.wasm"/"chunks/x.wasm",
|
|
31
|
-
// 不误报纯文件名字符串(错误消息 "physics.wasm" / debug 标签 "ringBurst.wasm" 无 /)。
|
|
32
|
-
const WASM_URL_RE = /['"][^'"]*\/[^'"/]*\.wasm(\?[^'"]*)?['"]/;
|
|
33
|
-
|
|
34
|
-
// 与 Layer 1 一致:扫描前剥注释(避免 dist 里残留的解释性注释/banner 误报)。
|
|
35
|
-
function stripComments(src) {
|
|
36
|
-
return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(?<!:)\/\/[^\n]*/g, '');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function tokensIn(rawContent) {
|
|
40
|
-
const content = stripComments(rawContent);
|
|
41
|
-
const hits = [];
|
|
42
|
-
for (const t of SUBSTRING_TOKENS) if (content.includes(t)) hits.push(t);
|
|
43
|
-
const m = content.match(WASM_URL_RE);
|
|
44
|
-
if (m) hits.push(m[0]);
|
|
45
|
-
return hits;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// dist 模块文件:.js + .mjs/.cjs(防止重运行时藏在非 .js chunk 里绕过)。
|
|
49
|
-
const DIST_MODULE_EXT = /\.(js|mjs|cjs)$/;
|
|
50
|
-
function jsFilesIn(dir, out = []) {
|
|
51
|
-
if (!existsSync(dir)) return out;
|
|
52
|
-
for (const e of readdirSync(dir)) {
|
|
53
|
-
const p = join(dir, e);
|
|
54
|
-
const s = statSync(p);
|
|
55
|
-
if (s.isDirectory()) jsFilesIn(p, out);
|
|
56
|
-
else if (DIST_MODULE_EXT.test(e)) out.push(p);
|
|
57
|
-
}
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 解析一个 dist 模块的相对 import(构 chunk 依赖图;裸包如 @babylonjs/* 不进图)。
|
|
62
|
-
function relImports(file) {
|
|
63
|
-
const src = readFileSync(file, 'utf8');
|
|
64
|
-
const specs = new Set();
|
|
65
|
-
let m;
|
|
66
|
-
const re = /\bfrom\s*['"]([^'"]+)['"]/g;
|
|
67
|
-
while ((m = re.exec(src))) specs.add(m[1]);
|
|
68
|
-
const side = /\bimport\s*['"]([^'"]+)['"]/g;
|
|
69
|
-
while ((m = side.exec(src))) specs.add(m[1]);
|
|
70
|
-
const dyn = /\bimport\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
71
|
-
while ((m = dyn.exec(src))) specs.add(m[1]);
|
|
72
|
-
const resolved = [];
|
|
73
|
-
for (const spec of specs) {
|
|
74
|
-
if (!spec.startsWith('.')) continue;
|
|
75
|
-
const target = resolve(dirname(file), spec);
|
|
76
|
-
const cands = [target, target + '.js', target + '.mjs', target + '.cjs',
|
|
77
|
-
join(target, 'index.js'), join(target, 'index.mjs'), join(target, 'index.cjs')];
|
|
78
|
-
for (const cand of cands) {
|
|
79
|
-
if (existsSync(cand) && statSync(cand).isFile()) { resolved.push(cand); break; }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return resolved;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// dist/<dir>/index.{js,mjs,cjs} 取第一个存在的入口。
|
|
86
|
-
function indexEntry(dir) {
|
|
87
|
-
for (const ext of ['js', 'mjs', 'cjs']) {
|
|
88
|
-
const p = join(dir, `index.${ext}`);
|
|
89
|
-
if (existsSync(p)) return p;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function reachableFrom(entry, importGraph) {
|
|
95
|
-
const seen = new Set();
|
|
96
|
-
const stack = [entry];
|
|
97
|
-
while (stack.length) {
|
|
98
|
-
const f = stack.pop();
|
|
99
|
-
if (seen.has(f)) continue;
|
|
100
|
-
seen.add(f);
|
|
101
|
-
for (const dep of importGraph.get(f) ?? []) if (!seen.has(dep)) stack.push(dep);
|
|
102
|
-
}
|
|
103
|
-
return seen;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ── 主流程 ────────────────────────────────────────────────────────────────────
|
|
107
|
-
if (!existsSync(DIST)) {
|
|
108
|
-
console.error(`✗ dist 不存在:${DIST}(先 \`pnpm build\`)。`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const allJs = jsFilesIn(DIST);
|
|
113
|
-
const importGraph = new Map(allJs.map((f) => [f, relImports(f)]));
|
|
114
|
-
const tokenByFile = new Map();
|
|
115
|
-
for (const f of allJs) {
|
|
116
|
-
const hits = tokensIn(readFileSync(f, 'utf8'));
|
|
117
|
-
if (hits.length) tokenByFile.set(f, [...new Set(hits)]);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 入口 = dist/index.{js,mjs,cjs}(runtime 核心)+ 每个 dist/effects/<id>/index.{js,mjs,cjs}。
|
|
121
|
-
const entries = [];
|
|
122
|
-
const rootIndex = indexEntry(DIST);
|
|
123
|
-
if (rootIndex) entries.push({ label: 'runtime (index)', entry: rootIndex });
|
|
124
|
-
const effectsDist = join(DIST, 'effects');
|
|
125
|
-
if (existsSync(effectsDist)) {
|
|
126
|
-
for (const id of readdirSync(effectsDist)) {
|
|
127
|
-
const idx = indexEntry(join(effectsDist, id));
|
|
128
|
-
if (idx) entries.push({ label: id, entry: idx });
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 归因:每个入口 → 它可达的、命中 token 的文件。
|
|
133
|
-
const findings = []; // {label, file, tokens}
|
|
134
|
-
const attributedFiles = new Set();
|
|
135
|
-
for (const { label, entry } of entries) {
|
|
136
|
-
const reach = reachableFrom(entry, importGraph);
|
|
137
|
-
for (const f of reach) {
|
|
138
|
-
if (tokenByFile.has(f)) {
|
|
139
|
-
attributedFiles.add(f);
|
|
140
|
-
findings.push({ label, file: f, tokens: tokenByFile.get(f) });
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// 没被任何入口可达但仍命中的文件(孤儿,理论上不该有)。
|
|
145
|
-
for (const [f, tokens] of tokenByFile) {
|
|
146
|
-
if (!attributedFiles.has(f)) findings.push({ label: '(未归因/孤儿 chunk)', file: f, tokens });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (findings.length === 0) {
|
|
150
|
-
console.log(`✅ Layer 2 dist 禁运行时扫描通过:${allJs.length} 个 js,0 命中(无 meshopt/draco/ktx2/brotli/wasm 运行时)。`);
|
|
151
|
-
process.exit(0);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
console.log(`❌ Layer 2 dist 禁运行时扫描:发现 ${findings.length} 处命中。\n`);
|
|
155
|
-
// 按特效聚合展示
|
|
156
|
-
const byLabel = {};
|
|
157
|
-
for (const f of findings) (byLabel[f.label] ??= []).push(f);
|
|
158
|
-
for (const [label, items] of Object.entries(byLabel)) {
|
|
159
|
-
console.log(` 特效/入口:${label}`);
|
|
160
|
-
for (const it of items) {
|
|
161
|
-
console.log(` ✗ ${relative(DIST, it.file)} ← ${it.tokens.join(', ')}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
console.log('\n修复:定位拉入该运行时的特效(多半是全量 glTF loader / 含 meshopt|draco|ktx2 的 GLB)。');
|
|
165
|
-
console.log(' 改用核心 loader、把 GLB 预解压成 plain、或用 ?url 内联的轻量资源。详见 Layer 1 提示与 #2 v0.1.1。');
|
|
166
|
-
process.exit(1);
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// 生成顶层 index.json:扫 packages/effects/<id>/,汇总 id/nameZh/kind/category/tags/media。
|
|
3
|
-
// 富检索字段(category/tags/media)优先取 description.json,避免与 manifest 双源。
|
|
4
|
-
import { readdirSync, statSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
5
|
-
import { resolve, dirname, join } from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
|
|
8
|
-
const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
-
const EFFECTS_DIR = join(LIB_ROOT, 'packages', 'effects');
|
|
10
|
-
|
|
11
|
-
function readJsonSafe(p) {
|
|
12
|
-
try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const effects = [];
|
|
16
|
-
for (const entry of readdirSync(EFFECTS_DIR)) {
|
|
17
|
-
const dir = join(EFFECTS_DIR, entry);
|
|
18
|
-
if (!statSync(dir).isDirectory()) continue;
|
|
19
|
-
const indexPath = join(dir, 'index.ts');
|
|
20
|
-
if (!existsSync(indexPath)) continue;
|
|
21
|
-
|
|
22
|
-
const effectJson = readJsonSafe(join(dir, 'effect.json')) ?? {};
|
|
23
|
-
const desc = readJsonSafe(join(dir, 'description.json')) ?? {};
|
|
24
|
-
const indexSrc = readFileSync(indexPath, 'utf8');
|
|
25
|
-
const kind = /createManifestRuntimeEffectPackage|createDataDrivenEffectPackage/.test(indexSrc) ? 'data' : 'code';
|
|
26
|
-
|
|
27
|
-
const mediaDir = join(dir, 'media');
|
|
28
|
-
const media = {};
|
|
29
|
-
if (existsSync(mediaDir)) {
|
|
30
|
-
for (const f of readdirSync(mediaDir)) {
|
|
31
|
-
if (/thumbnail/.test(f)) media.thumbnail = `packages/effects/${entry}/media/${f}`;
|
|
32
|
-
else if (/preview\.(mp4|webm|gif)$/.test(f)) media.preview = `packages/effects/${entry}/media/${f}`;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
effects.push({
|
|
37
|
-
id: effectJson.id || entry,
|
|
38
|
-
dir: entry,
|
|
39
|
-
nameZh: effectJson.nameZh || desc.searchTitleZh || entry,
|
|
40
|
-
kind,
|
|
41
|
-
category: desc.semanticTags?.[0] || (effectJson.id?.split('.')[0]) || null,
|
|
42
|
-
tags: desc.searchKeywordsEn || desc.visualTags || effectJson.search?.tags || [],
|
|
43
|
-
media,
|
|
44
|
-
path: `packages/effects/${entry}`,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
effects.sort((a, b) => a.id.localeCompare(b.id));
|
|
48
|
-
|
|
49
|
-
const out = {
|
|
50
|
-
schemaVersion: 'vfx-index/1.0',
|
|
51
|
-
count: effects.length,
|
|
52
|
-
effects,
|
|
53
|
-
};
|
|
54
|
-
writeFileSync(join(LIB_ROOT, 'index.json'), JSON.stringify(out, null, 2) + '\n');
|
|
55
|
-
console.log(`✅ index.json 生成:${effects.length} 个特效。`);
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// guardrail 回归用例(issue #3 验收最后一条):
|
|
3
|
-
// "故意造一个引全量 glTF 的特效 → 被 Layer 1 + Layer 2 双重拦下"。
|
|
4
|
-
// 全程 hermetic(写临时目录,不碰 packages/effects 与真实 dist),跑完即清理。
|
|
5
|
-
// Layer 1:对一批坏 fixture 跑 validate-effects,断言每个都被拒(exit≠0)+ 对干净 fixture 断言放行。
|
|
6
|
-
// Layer 2:造一个含 meshopt token 的假 dist chunk(= 全量 glTF loader 会拉进来的东西),
|
|
7
|
-
// 断言 check-dist-runtime 命中并归因到引它的特效;且 `basis` 变量名不被误报。
|
|
8
|
-
// 用法:node tooling/test-guardrails.mjs 退出码:全过 0,任一断言失败 1。
|
|
9
|
-
import { spawnSync } from 'node:child_process';
|
|
10
|
-
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
11
|
-
import { tmpdir } from 'node:os';
|
|
12
|
-
import { resolve, dirname, join } from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
-
|
|
15
|
-
const LIB_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
-
const tmp = mkdtempSync(join(tmpdir(), 'vfx-guardrail-'));
|
|
17
|
-
let failures = 0;
|
|
18
|
-
|
|
19
|
-
function runValidateEffects(effectsDir) {
|
|
20
|
-
return spawnSync('node', ['tooling/validate-effects.mjs', '--effects-dir', effectsDir], { cwd: LIB_ROOT, encoding: 'utf8' });
|
|
21
|
-
}
|
|
22
|
-
function runCheckDist(distDir) {
|
|
23
|
-
return spawnSync('node', ['tooling/check-dist-runtime.mjs', '--dist', distDir], { cwd: LIB_ROOT, encoding: 'utf8' });
|
|
24
|
-
}
|
|
25
|
-
function assert(cond, msg, detail = '') {
|
|
26
|
-
if (cond) { console.log(` ✅ ${msg}`); }
|
|
27
|
-
else { console.log(` ❌ ${msg}${detail ? `\n ${detail.replace(/\n/g, '\n ')}` : ''}`); failures += 1; }
|
|
28
|
-
}
|
|
29
|
-
function write(p, content) { mkdirSync(dirname(p), { recursive: true }); writeFileSync(p, content); }
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
// ── Layer 1 ────────────────────────────────────────────────────────────────
|
|
33
|
-
console.log('Layer 1(validate-effects)回归:');
|
|
34
|
-
const fxRoot = join(tmp, 'effects');
|
|
35
|
-
|
|
36
|
-
// 坏 1:引全量 glTF barrel(meshopt 事故的根因)
|
|
37
|
-
write(join(fxRoot, 'full-gltf', 'index.ts'),
|
|
38
|
-
`import '@babylonjs/loaders/glTF';\nexport const e = { id:'full-gltf', nameZh:'x', defaultParams:{}, debugParams:[], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
39
|
-
// 坏 2:new URL 资源 + 缺资源
|
|
40
|
-
write(join(fxRoot, 'new-url', 'index.ts'),
|
|
41
|
-
`const u = new URL('./assets/a.bin', import.meta.url);\nimport tex from './assets/missing.png?url';\nexport const e = { id:'new-url', nameZh:'x', defaultParams:{}, debugParams:[], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
42
|
-
// 坏 3:直接引解码器类(间接 token)
|
|
43
|
-
write(join(fxRoot, 'decoder-token', 'index.ts'),
|
|
44
|
-
`import { MeshoptDecoder } from '@babylonjs/core/Meshes/Compression/meshoptCompression';\nexport const e = { id:'decoder-token', nameZh:'x', defaultParams:{}, debugParams:[], create(){ MeshoptDecoder.UseInstances = true; return { root:{}, dispose(){} }; } };\n`);
|
|
45
|
-
// 坏 4:不是合法 package
|
|
46
|
-
write(join(fxRoot, 'not-a-package', 'index.ts'), `export const notAPackage = { hello: 'world' };\n`);
|
|
47
|
-
// 干净:核心 loader + ?url 存在 + 合法契约 + 注释里提 meshopt 不应误报
|
|
48
|
-
write(join(fxRoot, 'clean', 'assets', 'm.glb'), 'GLB');
|
|
49
|
-
write(join(fxRoot, 'clean', 'index.ts'),
|
|
50
|
-
`// 用核心 loader,避免 EXT_meshopt_compression / MeshoptDecoder 被拉进包\nimport '@babylonjs/loaders/glTF/2.0/glTFLoader';\nimport url from './assets/m.glb?url';\nexport const e = { id:'clean', nameZh:'干净', defaultParams:{ a:1 }, debugParams:[{key:'a',label:'a',kind:'number'}], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
51
|
-
write(join(fxRoot, 'clean', 'vfx-params.json'), `{ "schemaVersion":"vfx-params/1.0","effectId":"clean","params":{} }`);
|
|
52
|
-
|
|
53
|
-
// 一次扫整根:坏的应被拒
|
|
54
|
-
const allRes = runValidateEffects(fxRoot);
|
|
55
|
-
assert(allRes.status !== 0, '坏 fixture 集合被 Layer 1 拒绝(exit≠0)', allRes.stdout);
|
|
56
|
-
for (const [bad, needle] of [
|
|
57
|
-
['full-gltf', '@babylonjs/loaders/glTF'],
|
|
58
|
-
['new-url', 'new URL'],
|
|
59
|
-
['decoder-token', 'MeshoptDecoder'],
|
|
60
|
-
['not-a-package', '未检测到合法 VfxEffectPackage'],
|
|
61
|
-
]) {
|
|
62
|
-
assert(allRes.stdout.includes(bad) && allRes.stdout.includes(needle), `命中坏 fixture「${bad}」并给出原因`, allRes.stdout);
|
|
63
|
-
}
|
|
64
|
-
// 规避向量回归(对抗式审查发现并已堵住的绕过手法,每个都必须仍被拦):
|
|
65
|
-
const evasion = join(tmp, 'evasion');
|
|
66
|
-
// 把全量 barrel + MeshoptDecoder 藏在 helper .js 里(非 .ts sibling)
|
|
67
|
-
write(join(evasion, 'sibling-js', 'index.ts'), `import './heavy.js';\nexport const e={id:'x',nameZh:'x',defaultParams:{},debugParams:[],create(){return{root:{},dispose(){}};}};\n`);
|
|
68
|
-
write(join(evasion, 'sibling-js', 'heavy.js'), `import '@babylonjs/loaders/glTF';\nexport const z = MeshoptDecoder;\n`);
|
|
69
|
-
// 无空格副作用导入
|
|
70
|
-
write(join(evasion, 'nospace', 'index.ts'), `import'@babylonjs/loaders/glTF';\nexport const e={id:'x',nameZh:'x',defaultParams:{},debugParams:[],create(){return{root:{},dispose(){}};}};\n`);
|
|
71
|
-
// 模板字符串动态 import
|
|
72
|
-
write(join(evasion, 'tmpl-dyn', 'index.ts'), "export const e={id:'x',nameZh:'x',defaultParams:{},debugParams:[],async create(){await import(`@babylonjs/loaders/glTF`);return{root:{},dispose(){}};}};\n");
|
|
73
|
-
// 字符串拼接动态 import
|
|
74
|
-
write(join(evasion, 'concat', 'index.ts'), `export const e={id:'x',nameZh:'x',defaultParams:{},debugParams:[],async create(){await import('@babylonjs/loaders'+'/glTF');return{root:{},dispose(){}};}};\n`);
|
|
75
|
-
// 模板字符串 new URL
|
|
76
|
-
write(join(evasion, 'tmpl-url', 'index.ts'), "const u=new URL(`./a.glb`,import.meta.url);\nexport const e={id:'x',nameZh:'x',defaultParams:{},debugParams:[],create(){return{root:{},dispose(){}};}};\n");
|
|
77
|
-
const evRes = runValidateEffects(evasion);
|
|
78
|
-
assert(evRes.status !== 0, '规避向量集合被 Layer 1 拒绝(exit≠0)', evRes.stdout);
|
|
79
|
-
for (const id of ['sibling-js', 'nospace', 'tmpl-dyn', 'concat', 'tmpl-url']) {
|
|
80
|
-
assert(evRes.stdout.includes(id), `规避向量「${id}」被拦下`, evRes.stdout);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// 契约误报回归:箭头 create / 泛型 create / re-export barrel 都应放行(0 error)
|
|
84
|
-
const contractOk = join(tmp, 'contract-ok');
|
|
85
|
-
write(join(contractOk, 'arrow', 'index.ts'), `export const e={id:'x',nameZh:'测',defaultParams:{a:1},debugParams:[{key:'a',label:'a',kind:'number'}],create:(ctx)=>({root:ctx.root,dispose(){}})};\n`);
|
|
86
|
-
write(join(contractOk, 'arrow', 'vfx-params.json'), `{"schemaVersion":"vfx-params/1.0","effectId":"arrow","params":{}}`);
|
|
87
|
-
write(join(contractOk, 'barrel', 'index.ts'), `export * from './impl';\n`);
|
|
88
|
-
write(join(contractOk, 'barrel', 'impl.ts'), `export const e={id:'x',nameZh:'测',defaultParams:{},debugParams:[],create(){return{root:{},dispose(){}};}};\n`);
|
|
89
|
-
write(join(contractOk, 'barrel', 'vfx-params.json'), `{"schemaVersion":"vfx-params/1.0","effectId":"barrel","params":{}}`);
|
|
90
|
-
const contractRes = runValidateEffects(contractOk);
|
|
91
|
-
assert(contractRes.status === 0, '箭头 create / re-export barrel 合法写法被放行(无契约误报)', contractRes.stdout);
|
|
92
|
-
|
|
93
|
-
// 面板↔参数 key 一致性:debugParams 旋钮的 key 不在 defaultParams → 拦;对齐 → 放行
|
|
94
|
-
const keyBad = join(tmp, 'key-bad');
|
|
95
|
-
write(join(keyBad, 'orphan-knob', 'index.ts'),
|
|
96
|
-
`const DEFAULT_PARAMS = { lifetime: 1, scale: 2 };\nexport const e = { id:'orphan-knob', nameZh:'测', defaultParams: DEFAULT_PARAMS, debugParams: [ {key:'lifetime',label:'a',kind:'lifetime'}, {key:'scale',label:'b',kind:'scale'}, {key:'partcileCount',label:'拼错',kind:'number'} ], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
97
|
-
write(join(keyBad, 'orphan-knob', 'vfx-params.json'), `{"schemaVersion":"vfx-params/1.0","effectId":"orphan-knob","params":{}}`);
|
|
98
|
-
const keyBadRes = runValidateEffects(keyBad);
|
|
99
|
-
assert(keyBadRes.status !== 0 && /partcileCount/.test(keyBadRes.stdout), 'debugParams 旋钮 key 无对应 defaultParams 被拦(面板↔参数一致性)', keyBadRes.stdout);
|
|
100
|
-
|
|
101
|
-
const keyOk = join(tmp, 'key-ok');
|
|
102
|
-
write(join(keyOk, 'aligned', 'index.ts'),
|
|
103
|
-
`const DEFAULT_PARAMS = { lifetime: 1, count: 8 };\nexport const e = { id:'aligned', nameZh:'测', defaultParams: DEFAULT_PARAMS, debugParams: [ {key:'lifetime',label:'a',kind:'lifetime'}, {key:'count',label:'b',kind:'number'} ], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
104
|
-
write(join(keyOk, 'aligned', 'vfx-params.json'), `{"schemaVersion":"vfx-params/1.0","effectId":"aligned","params":{}}`);
|
|
105
|
-
const keyOkRes = runValidateEffects(keyOk);
|
|
106
|
-
assert(keyOkRes.status === 0, 'debugParams.key ⊆ defaultParams.key 时放行', keyOkRes.stdout);
|
|
107
|
-
|
|
108
|
-
// 干净 fixture 单独扫应放行
|
|
109
|
-
const cleanRoot = join(tmp, 'effects-clean');
|
|
110
|
-
write(join(cleanRoot, 'clean', 'assets', 'm.glb'), 'GLB');
|
|
111
|
-
write(join(cleanRoot, 'clean', 'index.ts'),
|
|
112
|
-
`// 注释提到 EXT_meshopt_compression / MeshoptDecoder 不应误报\nimport '@babylonjs/loaders/glTF/2.0/glTFLoader';\nimport url from './assets/m.glb?url';\nexport const e = { id:'clean', nameZh:'干净', defaultParams:{ a:1 }, debugParams:[{key:'a',label:'a',kind:'number'}], create(){ return { root:{}, dispose(){} }; } };\n`);
|
|
113
|
-
write(join(cleanRoot, 'clean', 'vfx-params.json'), `{ "schemaVersion":"vfx-params/1.0","effectId":"clean","params":{} }`);
|
|
114
|
-
const cleanRes = runValidateEffects(cleanRoot);
|
|
115
|
-
assert(cleanRes.status === 0, '干净 fixture 被放行(注释里的 meshopt 字样不误报)', cleanRes.stdout);
|
|
116
|
-
|
|
117
|
-
// ── Layer 2 ────────────────────────────────────────────────────────────────
|
|
118
|
-
console.log('\nLayer 2(check-dist-runtime)回归:');
|
|
119
|
-
const fakeDist = join(tmp, 'dist');
|
|
120
|
-
// 共享 heavy chunk = 全量 glTF loader 会拉进来的 meshopt 运行时
|
|
121
|
-
write(join(fakeDist, 'chunks', 'heavy-aaa.js'),
|
|
122
|
-
`export const EXT = "EXT_meshopt_compression";\nexport function setup(){ MeshoptDecoder.UseInstances = true; }\n`);
|
|
123
|
-
// light chunk:含 `basis` 变量名(必须不被误报)
|
|
124
|
-
write(join(fakeDist, 'chunks', 'light-bbb.js'), `export const ringBasis = "a normal basis variable";\n`);
|
|
125
|
-
write(join(fakeDist, 'index.js'), `import { ringBasis } from "./chunks/light-bbb.js";\nconsole.log(ringBasis);\n`);
|
|
126
|
-
write(join(fakeDist, 'effects', 'bad-effect', 'index.js'), `import { setup } from "../../chunks/heavy-aaa.js";\nsetup();\n`);
|
|
127
|
-
write(join(fakeDist, 'effects', 'clean-effect', 'index.js'), `import { ringBasis } from "../../chunks/light-bbb.js";\nconsole.log(ringBasis);\n`);
|
|
128
|
-
const distRes = runCheckDist(fakeDist);
|
|
129
|
-
assert(distRes.status !== 0, '含 meshopt 的假 dist 被 Layer 2 拦下(exit≠0)', distRes.stdout);
|
|
130
|
-
assert(distRes.stdout.includes('bad-effect') && /meshopt/i.test(distRes.stdout), '命中并归因到引入它的特效「bad-effect」', distRes.stdout);
|
|
131
|
-
assert(!distRes.stdout.includes('clean-effect'), '只引 light chunk 的「clean-effect」不被误报', distRes.stdout);
|
|
132
|
-
assert(!/✗.*basis['"]?\s*$/m.test(distRes.stdout) && !distRes.stdout.includes('light-bbb'), '`basis` 变量名不触发误报', distRes.stdout);
|
|
133
|
-
|
|
134
|
-
// Layer 2 鲁棒性回归:meshopt 藏 .mjs chunk 仍被拦
|
|
135
|
-
const distMjs = join(tmp, 'dist-mjs');
|
|
136
|
-
write(join(distMjs, 'chunks', 'heavy.mjs'), `export const x="EXT_meshopt_compression"; MeshoptDecoder.foo();\n`);
|
|
137
|
-
write(join(distMjs, 'effects', 'evil', 'index.js'), `import '../../chunks/heavy.mjs';\n`);
|
|
138
|
-
const mjsRes = runCheckDist(distMjs);
|
|
139
|
-
assert(mjsRes.status !== 0 && mjsRes.stdout.includes('evil'), 'meshopt 藏 .mjs chunk 仍被 Layer 2 拦下并归因', mjsRes.stdout);
|
|
140
|
-
|
|
141
|
-
// Layer 2 假阳性回归:.wasm 错误消息(无 /)+ 注释里的 token 都不应误报
|
|
142
|
-
const distFp = join(tmp, 'dist-fp');
|
|
143
|
-
write(join(distFp, 'effects', 'a', 'index.js'), `export function c(){ throw new Error("dropped optional physics.wasm"); }\n`);
|
|
144
|
-
write(join(distFp, 'effects', 'b', 'index.js'), `/** intentionally avoids MeshoptDecoder and DracoCompression */\nexport const e=1;\n`);
|
|
145
|
-
const fpRes = runCheckDist(distFp);
|
|
146
|
-
assert(fpRes.status === 0, '.wasm 文件名错误消息 + 注释里的禁 token 不触发 Layer 2 误报', fpRes.stdout);
|
|
147
|
-
} finally {
|
|
148
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
console.log('');
|
|
152
|
-
if (failures === 0) {
|
|
153
|
-
console.log('✅ guardrail 回归全部通过(全量 glTF 特效被 Layer 1 + Layer 2 双重拦下;无误报)。');
|
|
154
|
-
process.exit(0);
|
|
155
|
-
}
|
|
156
|
-
console.log(`❌ guardrail 回归:${failures} 条断言失败。`);
|
|
157
|
-
process.exit(1);
|