nccgs 2.1.1 → 3.0.0-rc.2
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 +76 -235
- package/LICENSE +1 -1
- package/README.md +87 -206
- package/THIRD_PARTY_NOTICES.md +6 -34
- package/bin/nccgs.mjs +96 -0
- package/cli/doctor.mjs +82 -0
- package/cli/feedback.mjs +13 -0
- package/cli/install.mjs +111 -0
- package/cli/release.mjs +59 -0
- package/cli/run.mjs +35 -0
- package/cli/spawn.mjs +20 -0
- package/cli/uninstall.mjs +43 -0
- package/cli/update-check.mjs +66 -0
- package/docs/EVIDENCE-VERIFICATION.md +22 -0
- package/package.json +23 -30
- package/runtime/core/diag.mjs +43 -0
- package/runtime/core/feedback-policy.mjs +54 -0
- package/runtime/core/fs.mjs +80 -0
- package/runtime/core/job-object.mjs +73 -0
- package/runtime/core/modes.mjs +87 -0
- package/runtime/core/process-tree.mjs +109 -0
- package/runtime/core/project.mjs +113 -0
- package/runtime/core/snapshot.mjs +114 -0
- package/runtime/hooks/common.mjs +22 -0
- package/runtime/hooks/done-guard.mjs +15 -0
- package/runtime/hooks/guard-mcp.mjs +8 -0
- package/runtime/hooks/guard-shell.mjs +16 -0
- package/runtime/hooks/guard-write.mjs +25 -0
- package/runtime/hooks/session-log.mjs +7 -0
- package/runtime/hooks/session-start.mjs +40 -0
- package/runtime/hooks/unity-transport.mjs +12 -0
- package/runtime/modes.json +39 -0
- package/runtime/task/cost.mjs +103 -0
- package/runtime/task/criteria.mjs +128 -0
- package/runtime/task/feedback.mjs +136 -0
- package/runtime/task/ledger.mjs +109 -0
- package/runtime/task/plan.mjs +69 -0
- package/runtime/task/project.mjs +79 -0
- package/runtime/task/report.mjs +252 -0
- package/runtime/task/task.mjs +238 -0
- package/runtime/unity/assets.mjs +51 -0
- package/runtime/unity/playtest.mjs +145 -0
- package/runtime/unity/unity.mjs +350 -0
- package/scripts/check-job-object.mjs +18 -0
- package/scripts/sync-unity-skills.mjs +35 -119
- package/template/CLAUDE.md +39 -0
- package/template/agents/fast-fix.md +9 -0
- package/template/agents/implementer.md +19 -0
- package/template/agents/playtester.md +15 -0
- package/template/agents/reviewer.md +19 -0
- package/template/agents/scout.md +9 -0
- package/template/ponytail/README.md +8 -0
- package/{.claude/nccgs/packages → template}/ponytail/engineering.md +10 -0
- package/template/protocols/evidence.md +52 -0
- package/template/protocols/playtest.md +45 -0
- package/template/protocols/review.md +34 -0
- package/template/protocols/unity-cli-notes.md +56 -0
- package/template/settings.fragment.json +33 -0
- package/template/skills/art-bible/SKILL.md +19 -0
- package/template/skills/asset-spec/SKILL.md +19 -0
- package/template/skills/brainstorm/SKILL.md +17 -0
- package/template/skills/feedback/SKILL.md +16 -0
- package/template/skills/map-systems/SKILL.md +25 -0
- package/template/skills/playtest/SKILL.md +17 -0
- package/template/skills/status/SKILL.md +18 -0
- package/template/skills/test/SKILL.md +34 -0
- package/template/skills/unity-foundation/SKILL.md +17 -0
- package/template/skills/vertical-slice/SKILL.md +17 -0
- package/template/skills/work/SKILL.md +65 -0
- package/tools/feedback-worker/README.md +12 -0
- package/tools/feedback-worker/worker.js +39 -0
- package/tools/feedback-worker/wrangler.toml +12 -0
- package/unity-skills/2d-pixel-perfect/SKILL.md +208 -0
- package/unity-skills/2d-pixel-perfect/references/api-reference.md +81 -0
- package/unity-skills/2d-pixel-perfect/references/camera-setup-builtin.cs +40 -0
- package/unity-skills/2d-pixel-perfect/references/camera-setup-urp.cs +49 -0
- package/unity-skills/2d-pixel-perfect/references/pipeline-detection.cs +50 -0
- package/unity-skills/2d-pixel-perfect/references/sprite-settings.cs +37 -0
- package/unity-skills/asset-transformer-toolkit/SECURITY.md +15 -0
- package/unity-skills/asset-transformer-toolkit/SKILL.md +39 -0
- package/unity-skills/asset-transformer-toolkit/references/create-importer.md +121 -0
- package/unity-skills/asset-transformer-toolkit/references/lods.md +61 -0
- package/unity-skills/asset-transformer-toolkit/references/rule-api.md +154 -0
- package/unity-skills/asset-transformer-toolkit/references/ruleblock-api.md +37 -0
- package/unity-skills/asset-transformer-toolkit/references/ruleset-api.md +138 -0
- package/unity-skills/asset-transformer-toolkit/references/rulesets-and-actions.md +227 -0
- package/unity-skills/audio-setup-mixers/SECURITY.md +16 -0
- package/unity-skills/audio-setup-mixers/SKILL.md +127 -0
- package/unity-skills/audio-setup-mixers/references/api.md +162 -0
- package/unity-skills/build-gtk/SKILL.md +242 -0
- package/unity-skills/build-gtk/references/graph-api.md +324 -0
- package/unity-skills/build-gtk/references/pitfalls.md +86 -0
- package/unity-skills/build-gtk/references/runtime-and-visualization.md +196 -0
- package/unity-skills/build-gtk/references/state-machine-api.md +286 -0
- package/unity-skills/build-live-game/SKILL.md +255 -0
- package/{.claude/skills → unity-skills}/build-live-game/references/achievements.md +132 -154
- package/{.claude/skills → unity-skills}/build-live-game/references/apis.md +10 -19
- package/{.claude/skills → unity-skills}/build-live-game/references/authentication.md +14 -20
- package/{.claude/skills → unity-skills}/build-live-game/references/battlepass.md +269 -225
- package/unity-skills/build-live-game/references/blueprints-and-samples.md +75 -0
- package/unity-skills/build-live-game/references/cloud-code-cloudbehaviours.md +1111 -0
- package/unity-skills/build-live-game/references/cloud-code-modules.md +1276 -0
- package/unity-skills/build-live-game/references/cloud-code.md +58 -0
- package/{.claude/skills → unity-skills}/build-live-game/references/cloud-save.md +15 -25
- package/{.claude/skills → unity-skills}/build-live-game/references/deployment.md +33 -18
- package/{.claude/skills → unity-skills}/build-live-game/references/player-account.md +31 -23
- package/{.claude/skills → unity-skills}/build-live-game/references/tooling.md +0 -56
- package/unity-skills/build-live-game/references/ugs-cli.md +86 -0
- package/unity-skills/craft-game-floors/SECURITY.md +23 -0
- package/unity-skills/craft-game-floors/SKILL.md +134 -0
- package/unity-skills/craft-game-floors/references/art-direction.md +85 -0
- package/unity-skills/craft-game-floors/references/camera-rigs.md +51 -0
- package/unity-skills/craft-game-floors/references/character-motion.md +35 -0
- package/unity-skills/craft-game-floors/references/game-audio.md +47 -0
- package/unity-skills/craft-game-floors/references/game-feel.md +68 -0
- package/unity-skills/craft-game-floors/references/game-shell.md +45 -0
- package/unity-skills/craft-game-floors/references/live-floor.md +35 -0
- package/unity-skills/craft-game-floors/references/placeholder-assets.md +35 -0
- package/unity-skills/craft-game-floors/references/scene-lighting.md +52 -0
- package/unity-skills/craft-game-floors/references/toon-shading.md +34 -0
- package/unity-skills/craft-game-floors/references/vfx.md +40 -0
- package/unity-skills/craft-game-floors/references/visual-scorecard.md +43 -0
- package/unity-skills/create-2d-physics/SECURITY.md +16 -0
- package/unity-skills/create-2d-physics/SKILL.md +128 -0
- package/unity-skills/create-2d-physics/references/2d-physics-core.md +155 -0
- package/unity-skills/create-2d-physics/references/legacy-2d-physics.md +45 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/SKILL.md +12 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/references/hexagonalruletile.md +71 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/references/palette-create.md +38 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile-createempty.md +35 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile-createfromsegment.md +212 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/references/ruletile.md +90 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/scripts/CreatePaletteTemplate.cs +148 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/scripts/ManualWorkflowExample.cs +63 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/scripts/RuleTileGenerator.cs +61 -0
- package/unity-skills/create-tile-palette-or-rule-tiles/scripts/TilemapRuleTileCreateFromSegment.cs +225 -0
- package/unity-skills/generate-editor-search-query/SECURITY.md +17 -0
- package/unity-skills/generate-editor-search-query/SKILL.md +117 -0
- package/unity-skills/generate-editor-search-query/references/open-search-window.md +70 -0
- package/unity-skills/generate-editor-search-query/references/query-patterns.md +124 -0
- package/{.claude/skills → unity-skills}/implement-in-app-purchases/SKILL.md +1 -2
- package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/api-notes.md +25 -28
- package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-convert-native-google-billing.md +1 -1
- package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-implement-iap-d2c.md +40 -5
- package/{.claude/skills → unity-skills}/initialize-ai-navigation/SKILL.md +4 -4
- package/{.claude/skills → unity-skills}/initialize-ai-navigation/references/navigation-system.md +3 -3
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/CHANGELOG.md +12 -0
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/README.md +9 -5
- package/unity-skills/levelplay-unity-integration/SKILL.md +442 -0
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/banner-api.md +15 -8
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/best-practices.md +604 -536
- package/unity-skills/levelplay-unity-integration/references/dependency-resolution.md +94 -0
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/ilrd-api.md +2 -2
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/initialization-api.md +221 -0
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/interstitial-api.md +11 -3
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/ios-setup.md +6 -34
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/migration-sdk-9.md +1 -1
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/privacy-settings.md +14 -29
- package/{.claude/skills → unity-skills}/levelplay-unity-integration/references/rewarded-api.md +20 -4
- package/unity-skills/levelplay-unity-integration/references/testing-and-validation.md +222 -0
- package/unity-skills/levelplay-unity-integration/references/troubleshooting.md +99 -0
- package/unity-skills/localization/SKILL.md +347 -0
- package/{.claude/skills → unity-skills}/localization/references/api-notes.md +4 -0
- package/unity-skills/localization/resources/L10nBatchProcessor.cs +191 -0
- package/unity-skills/manage-sprite-atlas/SKILL.md +351 -0
- package/unity-skills/manage-sprite-atlas/references/addressables-delivery.md +58 -0
- package/unity-skills/manage-sprite-atlas/references/api.md +382 -0
- package/unity-skills/manage-sprite-atlas/references/best-practices.md +183 -0
- package/unity-skills/manage-sprite-atlas/references/common-errors.md +123 -0
- package/unity-skills/manage-sprite-atlas/references/custom-packing.md +147 -0
- package/unity-skills/manage-sprite-atlas/references/manual-authoring.md +39 -0
- package/unity-skills/manage-sprite-atlas/resources/authoringvsruntime.cs +176 -0
- package/unity-skills/manage-sprite-atlas/resources/buildaddressablespostprocess.cs +59 -0
- package/unity-skills/manage-sprite-atlas/resources/cacheloadedatlases.cs +145 -0
- package/unity-skills/manage-sprite-atlas/resources/cleanupbatchoperations.cs +119 -0
- package/unity-skills/manage-sprite-atlas/resources/createandconfiguremasteratlas.cs +57 -0
- package/unity-skills/manage-sprite-atlas/resources/custompackersetup.cs +88 -0
- package/unity-skills/manage-sprite-atlas/resources/deprecatedmethods.cs +142 -0
- package/unity-skills/manage-sprite-atlas/resources/dontmodifywithoutsaving.cs +120 -0
- package/unity-skills/manage-sprite-atlas/resources/dontpackinruntimebuilds.cs +190 -0
- package/unity-skills/manage-sprite-atlas/resources/dontscriptspriteatlasineditor.cs +114 -0
- package/unity-skills/manage-sprite-atlas/resources/editorconfiguration.cs +95 -0
- package/unity-skills/manage-sprite-atlas/resources/enablespritepacking.cs +172 -0
- package/unity-skills/manage-sprite-atlas/resources/gridspritepacker.cs +50 -0
- package/unity-skills/manage-sprite-atlas/resources/handlelatebinding.cs +147 -0
- package/unity-skills/manage-sprite-atlas/resources/handlenullcases.cs +141 -0
- package/unity-skills/manage-sprite-atlas/resources/migratev1tov2.cs +335 -0
- package/unity-skills/manage-sprite-atlas/resources/multipagepacker.cs +80 -0
- package/unity-skills/manage-sprite-atlas/resources/optimizetexturesettings.cs +191 -0
- package/unity-skills/manage-sprite-atlas/resources/packerdebugging.cs +158 -0
- package/unity-skills/manage-sprite-atlas/resources/packtransform.cs +51 -0
- package/unity-skills/manage-sprite-atlas/resources/platformnames.cs +127 -0
- package/unity-skills/manage-sprite-atlas/resources/queryspritesefficiently.cs +222 -0
- package/unity-skills/manage-sprite-atlas/resources/registercallbacks.cs +152 -0
- package/unity-skills/manage-sprite-atlas/resources/runtimeatlasloader.cs +15 -0
- package/unity-skills/manage-sprite-atlas/resources/runtimespriteaccess.cs +19 -0
- package/unity-skills/manage-sprite-atlas/resources/savespriteatlasasset.cs +107 -0
- package/unity-skills/manage-sprite-atlas/resources/scriptablepacker.cs +81 -0
- package/unity-skills/manage-sprite-atlas/resources/setincludeinbuild.cs +164 -0
- package/unity-skills/manage-sprite-atlas/resources/sizeoptimizedpacker.cs +88 -0
- package/unity-skills/manage-sprite-atlas/resources/spriteatlaslatebinding.cs +123 -0
- package/unity-skills/manage-sprite-atlas/resources/spriteatlasprebuildgenerator.cs +219 -0
- package/unity-skills/manage-sprite-atlas/resources/spritedata.cs +35 -0
- package/unity-skills/manage-sprite-atlas/resources/spritepack.cs +39 -0
- package/unity-skills/manage-sprite-atlas/resources/texturedata.cs +15 -0
- package/unity-skills/manage-sprite-atlas/resources/updatebuildscripts.cs +357 -0
- package/unity-skills/manage-sprite-atlas/resources/updatesettingsconfiguration.cs +135 -0
- package/unity-skills/manage-sprite-atlas/resources/usevariantswisely.cs +179 -0
- package/unity-skills/manage-sprite-atlas/resources/variantatlas.cs +39 -0
- package/unity-skills/manifest.json +1155 -0
- package/unity-skills/migrate-birp-to-urp/SECURITY.md +16 -0
- package/unity-skills/migrate-birp-to-urp/SKILL.md +543 -0
- package/unity-skills/migrate-birp-to-urp/references/capturing-the-editor.md +66 -0
- package/unity-skills/migrate-birp-to-urp/references/complex-shader-situations.md +272 -0
- package/unity-skills/migrate-birp-to-urp/references/custom-shader-triage.md +123 -0
- package/unity-skills/migrate-birp-to-urp/references/implementation-patterns.md +529 -0
- package/unity-skills/migrate-birp-to-urp/references/migration-workflow.md +265 -0
- package/unity-skills/migrate-birp-to-urp/references/quality-settings-map.md +69 -0
- package/unity-skills/new-unity-project/SECURITY.md +26 -0
- package/unity-skills/new-unity-project/SKILL.md +386 -0
- package/unity-skills/new-unity-project/references/skill-setup.md +66 -0
- package/unity-skills/optimize-audio/SECURITY.md +16 -0
- package/{.claude/skills → unity-skills}/optimize-audio/SKILL.md +17 -17
- package/{.claude/skills → unity-skills}/optimize-audio/resources/audio-import-api.md +8 -5
- package/{.claude/skills → unity-skills}/optimize-text-mesh-pro/SKILL.md +1 -11
- package/unity-skills/optimize-web/SECURITY.md +16 -0
- package/{.claude/skills → unity-skills}/optimize-web/SKILL.md +164 -27
- package/unity-skills/optimize-web/scripts/WebOptimizer.cs +46 -0
- package/unity-skills/optimize-web/scripts/toktx-examples.sh +11 -0
- package/{.claude/skills → unity-skills}/physics-3d-collision/SKILL.md +2 -2
- package/unity-skills/physics-3d-collision/references/troubleshooting.md +8 -0
- package/unity-skills/project-auditor-fixes/SKILL.md +64 -0
- package/{.claude/skills → unity-skills}/setup-multiplayer-services/SKILL.md +1 -4
- package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/SKILL.md +1 -1
- package/{.claude/skills → unity-skills}/shader-graph-create-custom-node/SKILL.md +1 -1
- package/unity-skills/sprite-editor/SECURITY.md +16 -0
- package/{.claude/skills → unity-skills}/sprite-editor/SKILL.md +9 -9
- package/{.claude/skills → unity-skills}/sprite-editor/references/templates.md +1 -1
- package/unity-skills/sprite-segment-3x3grid/SKILL.md +136 -0
- package/unity-skills/sprite-segment-3x3grid/scripts/SpriteGridAnalysis.cs +119 -0
- package/{.claude/skills → unity-skills}/ui/SKILL.md +1 -1
- package/{.claude/skills → unity-skills}/ui-imgui/SKILL.md +1 -7
- package/{.claude/skills → unity-skills}/ui-ugui/SKILL.md +3 -9
- package/{.claude/skills → unity-skills}/ui-uitk/SKILL.md +8 -14
- package/{.claude/skills → unity-skills}/ui-uitk/references/custom-elements.md +1 -1
- package/unity-skills/unity-cli/CHANGELOG.md +515 -0
- package/unity-skills/unity-cli/SECURITY.md +68 -0
- package/unity-skills/unity-cli/SKILL.md +723 -0
- package/unity-skills/unity-cli/references/auth-license-cloud.md +318 -0
- package/unity-skills/unity-cli/references/build-run-test.md +555 -0
- package/unity-skills/unity-cli/references/cloud-automation.md +736 -0
- package/{.claude/skills → unity-skills}/unity-cli/references/collaboration.md +166 -105
- package/unity-skills/unity-cli/references/config-hub.md +239 -0
- package/unity-skills/unity-cli/references/diagnostics-maintenance.md +720 -0
- package/unity-skills/unity-cli/references/docs-workflow.md +179 -0
- package/unity-skills/unity-cli/references/editors-install.md +476 -0
- package/unity-skills/unity-cli/references/integration-advanced.md +1130 -0
- package/unity-skills/unity-cli/references/playmode-verification-loop.md +279 -0
- package/unity-skills/unity-cli/references/projects-templates.md +996 -0
- package/unity-skills/unity-cli/references/version-control.md +612 -0
- package/{.claude/skills → unity-skills}/unity-package-management/SKILL.md +60 -12
- package/{.claude/skills → unity-skills}/unity-package-management/references/select-packages.md +39 -21
- package/unity-skills/unity-pipeline/SECURITY.md +17 -0
- package/unity-skills/unity-pipeline/SKILL.md +335 -0
- package/unity-skills/urp-postprocessing/SECURITY.md +16 -0
- package/{.claude/skills → unity-skills}/urp-postprocessing/SKILL.md +12 -12
- package/{.claude/skills → unity-skills}/urp-postprocessing/references/code-templates.md +39 -8
- package/{.claude/skills → unity-skills}/validate-urp-render-graph-renderer-feature/SKILL.md +1 -1
- package/.claude/agents/nccgs-adversarial-reviewer.md +0 -43
- package/.claude/agents/nccgs-engine-programmer.md +0 -27
- package/.claude/agents/nccgs-fast-implementer.md +0 -30
- package/.claude/agents/nccgs-game-designer.md +0 -26
- package/.claude/agents/nccgs-gameplay-programmer.md +0 -26
- package/.claude/agents/nccgs-qa-engineer.md +0 -27
- package/.claude/agents/nccgs-task-scout.md +0 -24
- package/.claude/agents/nccgs-ui-programmer.md +0 -27
- package/.claude/agents/nccgs-unity-build-specialist.md +0 -27
- package/.claude/agents/nccgs-unity-implementer.md +0 -28
- package/.claude/agents/nccgs-unity-systems-specialist.md +0 -27
- package/.claude/agents/nccgs-unity-ui-specialist.md +0 -27
- package/.claude/agents/nccgs-verification-engineer.md +0 -27
- package/.claude/nccgs/MEMORY.md +0 -66
- package/.claude/nccgs/THIRD_PARTY_NOTICES.md +0 -29
- package/.claude/nccgs/VERSION +0 -1
- package/.claude/nccgs/agent-library/nccgs-accessibility-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-adversarial-reviewer.md +0 -41
- package/.claude/nccgs/agent-library/nccgs-ai-programmer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-analytics-engineer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-art-direction-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-audio-direction-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-creative-director.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-documentation-manager.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-economy-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-engine-programmer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-fast-implementer.md +0 -28
- package/.claude/nccgs/agent-library/nccgs-game-design-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-game-designer.md +0 -24
- package/.claude/nccgs/agent-library/nccgs-gameplay-programmer.md +0 -24
- package/.claude/nccgs/agent-library/nccgs-level-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-live-ops-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-localization-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-narrative-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-network-programmer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-performance-analyst.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-producer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-production-coordinator.md +0 -22
- package/.claude/nccgs/agent-library/nccgs-programming-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-prototyper.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-qa-engineer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-qa-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-release-engineer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-release-lead.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-security-engineer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-sound-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-systems-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-task-scout.md +0 -22
- package/.claude/nccgs/agent-library/nccgs-technical-architect.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-technical-artist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-technical-director.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-tools-programmer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-ui-programmer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-unity-build-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-unity-content-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-unity-implementer.md +0 -26
- package/.claude/nccgs/agent-library/nccgs-unity-rendering-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-unity-systems-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-unity-ui-specialist.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-ux-designer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-verification-engineer.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-world-builder.md +0 -25
- package/.claude/nccgs/agent-library/nccgs-writer.md +0 -25
- package/.claude/nccgs/constitution.md +0 -187
- package/.claude/nccgs/hooks/agent-audit.mjs +0 -16
- package/.claude/nccgs/hooks/closure-guard.mjs +0 -59
- package/.claude/nccgs/hooks/common.mjs +0 -42
- package/.claude/nccgs/hooks/post-compact.mjs +0 -1
- package/.claude/nccgs/hooks/pre-compact.mjs +0 -8
- package/.claude/nccgs/hooks/protect-git.mjs +0 -22
- package/.claude/nccgs/hooks/protect-write.mjs +0 -17
- package/.claude/nccgs/hooks/runtime-observation.mjs +0 -11
- package/.claude/nccgs/hooks/session-start.mjs +0 -29
- package/.claude/nccgs/hooks/session-stop.mjs +0 -7
- package/.claude/nccgs/hooks/unity-transport-guard.mjs +0 -11
- package/.claude/nccgs/packages/ponytail/README.md +0 -46
- package/.claude/nccgs/packages/ponytail/manifest.json +0 -24
- package/.claude/nccgs/pipeline.json +0 -260
- package/.claude/nccgs/protocols/agent-contract.md +0 -101
- package/.claude/nccgs/protocols/astra-claude.md +0 -111
- package/.claude/nccgs/protocols/bounded-repair.md +0 -246
- package/.claude/nccgs/protocols/context-packets.md +0 -23
- package/.claude/nccgs/protocols/delivery-acceptance.md +0 -105
- package/.claude/nccgs/protocols/design-lifecycle.md +0 -139
- package/.claude/nccgs/protocols/editor-session-evidence.md +0 -52
- package/.claude/nccgs/protocols/evidence.md +0 -31
- package/.claude/nccgs/protocols/model-routing.md +0 -46
- package/.claude/nccgs/protocols/orchestration.md +0 -74
- package/.claude/nccgs/protocols/review-handoff.md +0 -101
- package/.claude/nccgs/protocols/shared-workflow-context.md +0 -33
- package/.claude/nccgs/protocols/standalone-gates.md +0 -329
- package/.claude/nccgs/protocols/standalone-pilot.md +0 -144
- package/.claude/nccgs/protocols/standalone.md +0 -207
- package/.claude/nccgs/protocols/task-autonomy.md +0 -55
- package/.claude/nccgs/protocols/task-gates.md +0 -144
- package/.claude/nccgs/protocols/task-slices.md +0 -106
- package/.claude/nccgs/protocols/unity-boundary.md +0 -31
- package/.claude/nccgs/routing.generated.md +0 -49
- package/.claude/nccgs/settings.fragment.json +0 -213
- package/.claude/nccgs/studio.json +0 -472
- package/.claude/nccgs/tools/approve-overrides.mjs +0 -81
- package/.claude/nccgs/tools/autonomy.mjs +0 -40
- package/.claude/nccgs/tools/command-process.mjs +0 -50
- package/.claude/nccgs/tools/command-tokens.mjs +0 -22
- package/.claude/nccgs/tools/compile-policy.mjs +0 -121
- package/.claude/nccgs/tools/configure-models.mjs +0 -96
- package/.claude/nccgs/tools/criteria.mjs +0 -121
- package/.claude/nccgs/tools/delivery-budget.mjs +0 -52
- package/.claude/nccgs/tools/design-readiness.mjs +0 -158
- package/.claude/nccgs/tools/editor-session-evidence.mjs +0 -66
- package/.claude/nccgs/tools/evidence.mjs +0 -153
- package/.claude/nccgs/tools/gates.mjs +0 -297
- package/.claude/nccgs/tools/handoff.mjs +0 -51
- package/.claude/nccgs/tools/hook-transport.mjs +0 -34
- package/.claude/nccgs/tools/inputs.mjs +0 -108
- package/.claude/nccgs/tools/installation-guard.mjs +0 -55
- package/.claude/nccgs/tools/integrity.mjs +0 -69
- package/.claude/nccgs/tools/lifecycle.mjs +0 -542
- package/.claude/nccgs/tools/native-completion.mjs +0 -30
- package/.claude/nccgs/tools/orchestration.mjs +0 -680
- package/.claude/nccgs/tools/paths.mjs +0 -60
- package/.claude/nccgs/tools/policy.mjs +0 -107
- package/.claude/nccgs/tools/process-lock.mjs +0 -74
- package/.claude/nccgs/tools/repair-packet.mjs +0 -106
- package/.claude/nccgs/tools/returned-handoff.mjs +0 -47
- package/.claude/nccgs/tools/review-budget.mjs +0 -171
- package/.claude/nccgs/tools/review-handoff.mjs +0 -66
- package/.claude/nccgs/tools/runtime.mjs +0 -71
- package/.claude/nccgs/tools/session-budget.mjs +0 -67
- package/.claude/nccgs/tools/shared-context-guard.mjs +0 -60
- package/.claude/nccgs/tools/shared-context.mjs +0 -25
- package/.claude/nccgs/tools/task-slices.mjs +0 -147
- package/.claude/nccgs/tools/task.mjs +0 -145
- package/.claude/nccgs/tools/tool-guard.mjs +0 -120
- package/.claude/nccgs/tools/unity-repair.mjs +0 -128
- package/.claude/nccgs/tools/unity-transport.mjs +0 -170
- package/.claude/nccgs/tools/verification-jobs.mjs +0 -171
- package/.claude/nccgs/tools/verification-worker.mjs +0 -71
- package/.claude/nccgs/tools/windows-compiler-server.cs +0 -163
- package/.claude/nccgs/tools/windows-job.cs +0 -103
- package/.claude/nccgs/unity-skills-manifest.json +0 -727
- package/.claude/nccgs/workflow-catalog.json +0 -654
- package/.claude/rules/nccgs-canonical-docs.md +0 -19
- package/.claude/rules/nccgs-editor-tools.md +0 -12
- package/.claude/rules/nccgs-localization.md +0 -12
- package/.claude/rules/nccgs-networking.md +0 -13
- package/.claude/rules/nccgs-performance.md +0 -14
- package/.claude/rules/nccgs-rendering.md +0 -16
- package/.claude/rules/nccgs-security.md +0 -13
- package/.claude/rules/nccgs-tests.md +0 -18
- package/.claude/rules/nccgs-ui.md +0 -14
- package/.claude/rules/nccgs-unity-assets.md +0 -21
- package/.claude/rules/nccgs-unity-code.md +0 -22
- package/.claude/skills/accessibility-review/SKILL.md +0 -22
- package/.claude/skills/accessibility-review/references/astra.md +0 -17
- package/.claude/skills/accessibility-review/references/legacy.md +0 -7
- package/.claude/skills/architecture-decision/SKILL.md +0 -22
- package/.claude/skills/architecture-decision/references/astra.md +0 -12
- package/.claude/skills/architecture-decision/references/legacy.md +0 -7
- package/.claude/skills/asset-audit/SKILL.md +0 -22
- package/.claude/skills/asset-audit/references/astra.md +0 -17
- package/.claude/skills/asset-audit/references/legacy.md +0 -7
- package/.claude/skills/audit/SKILL.md +0 -22
- package/.claude/skills/audit/references/astra.md +0 -17
- package/.claude/skills/audit/references/dimensions.md +0 -46
- package/.claude/skills/audit/references/legacy.md +0 -9
- package/.claude/skills/balance-review/SKILL.md +0 -22
- package/.claude/skills/balance-review/references/astra.md +0 -17
- package/.claude/skills/balance-review/references/legacy.md +0 -7
- package/.claude/skills/bug-triage/SKILL.md +0 -22
- package/.claude/skills/bug-triage/references/astra.md +0 -27
- package/.claude/skills/bug-triage/references/legacy.md +0 -7
- package/.claude/skills/build-live-game/SKILL.md +0 -317
- package/.claude/skills/build-live-game/references/cloud-code.md +0 -563
- package/.claude/skills/closure/SKILL.md +0 -22
- package/.claude/skills/closure/references/astra.md +0 -15
- package/.claude/skills/closure/references/legacy.md +0 -7
- package/.claude/skills/compatibility-review/SKILL.md +0 -22
- package/.claude/skills/compatibility-review/references/astra.md +0 -17
- package/.claude/skills/compatibility-review/references/legacy.md +0 -7
- package/.claude/skills/context-pack/SKILL.md +0 -22
- package/.claude/skills/context-pack/references/astra.md +0 -12
- package/.claude/skills/context-pack/references/legacy.md +0 -7
- package/.claude/skills/dependency-review/SKILL.md +0 -22
- package/.claude/skills/dependency-review/references/astra.md +0 -17
- package/.claude/skills/dependency-review/references/legacy.md +0 -7
- package/.claude/skills/design/SKILL.md +0 -24
- package/.claude/skills/design/references/astra.md +0 -12
- package/.claude/skills/design/references/legacy.md +0 -13
- package/.claude/skills/design-review/SKILL.md +0 -24
- package/.claude/skills/design-review/references/astra.md +0 -17
- package/.claude/skills/design-review/references/legacy.md +0 -7
- package/.claude/skills/evidence-review/SKILL.md +0 -22
- package/.claude/skills/evidence-review/references/astra.md +0 -17
- package/.claude/skills/evidence-review/references/legacy.md +0 -7
- package/.claude/skills/hotfix/SKILL.md +0 -22
- package/.claude/skills/hotfix/references/astra.md +0 -30
- package/.claude/skills/hotfix/references/legacy.md +0 -7
- package/.claude/skills/incident-recovery/SKILL.md +0 -22
- package/.claude/skills/incident-recovery/references/astra.md +0 -27
- package/.claude/skills/incident-recovery/references/legacy.md +0 -7
- package/.claude/skills/levelplay-unity-integration/SKILL.md +0 -1126
- package/.claude/skills/localization/SKILL.md +0 -135
- package/.claude/skills/localization/resources/L10nBatchProcessor.cs +0 -69
- package/.claude/skills/localize-game/SKILL.md +0 -22
- package/.claude/skills/localize-game/references/astra.md +0 -27
- package/.claude/skills/localize-game/references/legacy.md +0 -7
- package/.claude/skills/migrate-project/SKILL.md +0 -22
- package/.claude/skills/migrate-project/references/astra.md +0 -30
- package/.claude/skills/migrate-project/references/legacy.md +0 -19
- package/.claude/skills/migrate-project/references/procedure.md +0 -96
- package/.claude/skills/milestone-review/SKILL.md +0 -22
- package/.claude/skills/milestone-review/references/astra.md +0 -12
- package/.claude/skills/milestone-review/references/legacy.md +0 -7
- package/.claude/skills/nccgs-code-review/SKILL.md +0 -22
- package/.claude/skills/nccgs-code-review/references/astra.md +0 -21
- package/.claude/skills/nccgs-code-review/references/legacy.md +0 -7
- package/.claude/skills/new-unity-project/SKILL.md +0 -179
- package/.claude/skills/performance-audit/SKILL.md +0 -22
- package/.claude/skills/performance-audit/references/astra.md +0 -17
- package/.claude/skills/performance-audit/references/legacy.md +0 -7
- package/.claude/skills/physics-3d-collision/references/troubleshooting.md +0 -41
- package/.claude/skills/plan-feature/SKILL.md +0 -22
- package/.claude/skills/plan-feature/references/astra.md +0 -12
- package/.claude/skills/plan-feature/references/legacy.md +0 -7
- package/.claude/skills/playtest/SKILL.md +0 -22
- package/.claude/skills/playtest/references/astra.md +0 -31
- package/.claude/skills/playtest/references/legacy.md +0 -7
- package/.claude/skills/project-stage/SKILL.md +0 -22
- package/.claude/skills/project-stage/references/astra.md +0 -12
- package/.claude/skills/project-stage/references/legacy.md +0 -7
- package/.claude/skills/prototype-feature/SKILL.md +0 -22
- package/.claude/skills/prototype-feature/references/astra.md +0 -31
- package/.claude/skills/prototype-feature/references/legacy.md +0 -7
- package/.claude/skills/qa-plan/SKILL.md +0 -22
- package/.claude/skills/qa-plan/references/astra.md +0 -12
- package/.claude/skills/qa-plan/references/legacy.md +0 -7
- package/.claude/skills/release/SKILL.md +0 -22
- package/.claude/skills/release/references/astra.md +0 -30
- package/.claude/skills/release/references/legacy.md +0 -11
- package/.claude/skills/release-readiness/SKILL.md +0 -22
- package/.claude/skills/release-readiness/references/astra.md +0 -17
- package/.claude/skills/release-readiness/references/legacy.md +0 -7
- package/.claude/skills/retrospective/SKILL.md +0 -22
- package/.claude/skills/retrospective/references/astra.md +0 -12
- package/.claude/skills/retrospective/references/legacy.md +0 -7
- package/.claude/skills/review/SKILL.md +0 -22
- package/.claude/skills/review/references/astra.md +0 -21
- package/.claude/skills/review/references/legacy.md +0 -9
- package/.claude/skills/security-audit/SKILL.md +0 -22
- package/.claude/skills/security-audit/references/astra.md +0 -17
- package/.claude/skills/security-audit/references/legacy.md +0 -7
- package/.claude/skills/sprint-plan/SKILL.md +0 -22
- package/.claude/skills/sprint-plan/references/astra.md +0 -12
- package/.claude/skills/sprint-plan/references/legacy.md +0 -7
- package/.claude/skills/status/SKILL.md +0 -22
- package/.claude/skills/status/references/astra.md +0 -19
- package/.claude/skills/status/references/legacy.md +0 -9
- package/.claude/skills/story-readiness/SKILL.md +0 -24
- package/.claude/skills/story-readiness/references/astra.md +0 -17
- package/.claude/skills/story-readiness/references/legacy.md +0 -7
- package/.claude/skills/test/SKILL.md +0 -22
- package/.claude/skills/test/references/astra.md +0 -27
- package/.claude/skills/test/references/legacy.md +0 -9
- package/.claude/skills/ui-review/SKILL.md +0 -22
- package/.claude/skills/ui-review/references/astra.md +0 -17
- package/.claude/skills/ui-review/references/legacy.md +0 -7
- package/.claude/skills/unity-cli/CHANGELOG.md +0 -233
- package/.claude/skills/unity-cli/SECURITY.md +0 -22
- package/.claude/skills/unity-cli/SKILL.md +0 -414
- package/.claude/skills/unity-cli/references/auth-license-cloud.md +0 -146
- package/.claude/skills/unity-cli/references/build-run-test.md +0 -349
- package/.claude/skills/unity-cli/references/config-hub.md +0 -103
- package/.claude/skills/unity-cli/references/diagnostics-maintenance.md +0 -326
- package/.claude/skills/unity-cli/references/editors-install.md +0 -327
- package/.claude/skills/unity-cli/references/integration-advanced.md +0 -472
- package/.claude/skills/unity-cli/references/projects-templates.md +0 -574
- package/.claude/skills/work/SKILL.md +0 -22
- package/.claude/skills/work/references/astra.md +0 -32
- package/.claude/skills/work/references/classification.md +0 -41
- package/.claude/skills/work/references/closure.md +0 -60
- package/.claude/skills/work/references/feature-contracts.md +0 -36
- package/.claude/skills/work/references/legacy.md +0 -29
- package/.claude/skills/work/references/verification.md +0 -26
- package/CLAUDE.md +0 -10
- package/MEMORY.md +0 -109
- package/UPGRADING.md +0 -130
- package/VERSION +0 -1
- package/docs/ARCHITECTURE.md +0 -131
- package/docs/ASSET-PRODUCTION-GRAPHIFY-PLAN.md +0 -230
- package/docs/BOUNDED-REPAIR.md +0 -80
- package/docs/EDITOR-SESSION-EVIDENCE.md +0 -52
- package/docs/HANDOFF-AUTOMATION.md +0 -144
- package/docs/HUONG-DAN-MIGRATE-VA-SU-DUNG.md +0 -117
- package/docs/MIGRATION-MATRIX.md +0 -23
- package/docs/NCCGS-1.3.md +0 -13
- package/docs/NCCGS-1.4.1.md +0 -149
- package/docs/NCCGS-1.4.md +0 -184
- package/docs/NCCGS-1.5-IMPLEMENTATION-PLAN.md +0 -378
- package/docs/NCCGS-1.5-PILOT.md +0 -130
- package/docs/NCCGS-1.5.md +0 -237
- package/docs/PHASE-1-OPERATIONS.md +0 -278
- package/docs/PHASE-2-OPERATIONS.md +0 -183
- package/docs/PROJECT-POLICY.md +0 -94
- package/docs/RELEASE-STATUS.md +0 -150
- package/docs/TASK-SLICING.md +0 -57
- package/docs/UNINSTALL.md +0 -134
- package/docs/UPGRADE-QUALIFICATION.md +0 -45
- package/docs/WFK17-HOOK-REPAIR.md +0 -71
- package/docs/WORKFLOWS.md +0 -85
- package/package-assets/nccgs-project.gitignore +0 -2
- package/package-assets/setup-vivox-voice-chat-evals.gitignore +0 -7
- package/scaffold/.nccgs/autonomy.json +0 -4
- package/scaffold/.nccgs/bugs/.gitkeep +0 -1
- package/scaffold/.nccgs/closures/.gitkeep +0 -1
- package/scaffold/.nccgs/context/.gitkeep +0 -1
- package/scaffold/.nccgs/decisions/.gitkeep +0 -1
- package/scaffold/.nccgs/evidence/.gitkeep +0 -1
- package/scaffold/.nccgs/execution.json +0 -10
- package/scaffold/.nccgs/features/.gitkeep +0 -1
- package/scaffold/.nccgs/gates.json +0 -7
- package/scaffold/.nccgs/inputs.json +0 -6
- package/scaffold/.nccgs/migrations/.gitkeep +0 -1
- package/scaffold/.nccgs/playtests/.gitkeep +0 -1
- package/scaffold/.nccgs/project.yaml +0 -109
- package/scaffold/.nccgs/requirements.yaml +0 -10
- package/scaffold/.nccgs/reviews/.gitkeep +0 -1
- package/scaffold/.nccgs/state.md +0 -40
- package/scaffold/.nccgs/templates/agent-handoff.md +0 -37
- package/scaffold/.nccgs/templates/architecture-decision.md +0 -27
- package/scaffold/.nccgs/templates/closure-record.md +0 -62
- package/scaffold/.nccgs/templates/context-packet.yaml +0 -17
- package/scaffold/.nccgs/templates/design-contract.json +0 -34
- package/scaffold/.nccgs/templates/evidence-record.md +0 -23
- package/scaffold/.nccgs/templates/feature-contract.md +0 -60
- package/scaffold/.nccgs/templates/implementation-report.md +0 -52
- package/scaffold/.nccgs/templates/migration-plan.md +0 -40
- package/scaffold/.nccgs/templates/repair-packet.json +0 -14
- package/scaffold/.nccgs/templates/repair-unity-code.json +0 -18
- package/scaffold/.nccgs/templates/repair-unity-editor.json +0 -21
- package/scaffold/.nccgs/templates/task-slices.json +0 -28
- package/scaffold/.nccgs/templates/waiver.md +0 -11
- package/scripts/benchmark-snapshot.mjs +0 -205
- package/scripts/benchmark-v15.mjs +0 -173
- package/scripts/cli.mjs +0 -205
- package/scripts/doctor.mjs +0 -261
- package/scripts/install.mjs +0 -530
- package/scripts/recover-session.mjs +0 -89
- package/scripts/run.mjs +0 -86
- package/scripts/supervise.mjs +0 -152
- package/scripts/uninstall.mjs +0 -292
- package/scripts/validate.mjs +0 -285
- package/scripts/work-handoff.mjs +0 -38
- package/tests/autonomy.test.mjs +0 -126
- package/tests/benchmark-v15.test.mjs +0 -65
- package/tests/design-readiness.test.mjs +0 -200
- package/tests/editor-session-evidence.test.mjs +0 -45
- package/tests/evidence.test.mjs +0 -186
- package/tests/fixtures/compiler-shutdown.cs +0 -67
- package/tests/fixtures/native-provider.mjs +0 -61
- package/tests/framework.test.mjs +0 -740
- package/tests/gates.test.mjs +0 -479
- package/tests/hook-repair.test.mjs +0 -106
- package/tests/lifecycle-v15.test.mjs +0 -346
- package/tests/native-completion-notices.test.mjs +0 -181
- package/tests/orchestration-v15.test.mjs +0 -596
- package/tests/phase1.test.mjs +0 -427
- package/tests/phase2.test.mjs +0 -250
- package/tests/policy-v15.test.mjs +0 -234
- package/tests/ponytail-package.test.mjs +0 -75
- package/tests/returned-handoff-notices.test.mjs +0 -170
- package/tests/review-budget.test.mjs +0 -177
- package/tests/runtime-v15.test.mjs +0 -1198
- package/tests/session-budget.test.mjs +0 -34
- package/tests/task-slices.test.mjs +0 -285
- package/tests/uninstall.test.mjs +0 -477
- package/tests/unity-generated.test.mjs +0 -140
- package/tests/unity-transport.test.mjs +0 -348
- /package/{.claude/nccgs/packages → template}/ponytail/LICENSE +0 -0
- /package/{.claude/nccgs/packages → template}/ponytail/NOTICE.md +0 -0
- /package/{.claude/nccgs/packages → template}/ponytail/complexity-review.md +0 -0
- /package/{.claude/skills → unity-skills}/build-live-game/references/remote-config.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/README.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/codeless-catalog.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-adapty.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-essentialkit.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-revenuecat.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/convert-unipay.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/migration-v4-to-v5.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-add-iap-to-new-project.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/path-convert-native-storekit.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/platform-notes.md +0 -0
- /package/{.claude/skills → unity-skills}/implement-in-app-purchases/references/pre-check.md +0 -0
- /package/{.claude/skills → unity-skills}/localization/resources/LocalizedFontAsset.cs +0 -0
- /package/{.claude/skills → unity-skills}/optimize-audio/resources/platform-settings.md +0 -0
- /package/{.claude/skills → unity-skills}/optimize-web/resources/WebOptimizer.cs +0 -0
- /package/{.claude/skills → unity-skills}/optimize-web/resources/toktx-examples.sh +0 -0
- /package/{.claude/skills → unity-skills}/physics-3d-collision/resources/CollisionDebugger.cs +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/dgs-entrypoint.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/entrypoints.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/examples.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/implementation-fit.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/underlying-services.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-multiplayer-services/references/workflows-prerequisites.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/.env.example +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/README.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/promptfooconfig.yaml +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/init-and-login.yaml +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/text-chat.yaml +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/evals/tests/voice-channels.yaml +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/events-and-participants.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/init-and-login.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/text-chat.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/troubleshooting.md +0 -0
- /package/{.claude/skills → unity-skills}/setup-vivox-voice-chat/references/voice-channels.md +0 -0
- /package/{.claude/skills → unity-skills}/shader-graph-create-custom-node/resources/all_hints.hlsl +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/references/api_reference.md +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/references/background.md +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/AutomaticSliceTexture.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GenerateNewSpriteRects.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GetTextureSourceImageSize.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GetTextureToSlice.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/GridSliceTexture.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/IsometricSliceTexture.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/README.md +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/SetPivotExample.cs +0 -0
- /package/{.claude/skills → unity-skills}/sprite-editor/scripts/SpriteToPng.cs +0 -0
- /package/{.claude/skills → unity-skills}/ui-imgui/references/gui-elements.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-imgui/references/templates.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-ugui/references/scrollview-setup.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/common-issues.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/painter2d.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/pointermanipulator-guide.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/svg-icons.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/ui-runtime-binding.md +0 -0
- /package/{.claude/skills → unity-skills}/ui-uitk/references/uss-guide.md +0 -0
- /package/{.claude/skills → unity-skills}/urp-postprocessing/references/effect-reference.md +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Import-setting check against the art bible: one `script eval` reads each TextureImporter and returns
|
|
2
|
+
// the fields; mismatches are computed here. No .meta parsing, no hand-typed values.
|
|
3
|
+
import { evalCode } from "./unity.mjs";
|
|
4
|
+
|
|
5
|
+
const FIELDS = ["pixelsPerUnit", "filterMode", "textureType", "maxTextureSize", "spritePivot", "mipmapEnabled", "width", "height"];
|
|
6
|
+
|
|
7
|
+
export function assetScript(paths) {
|
|
8
|
+
const list = paths.map(p => JSON.stringify(p)).join(", ");
|
|
9
|
+
return `var sb = new System.Text.StringBuilder(); sb.Append("[");
|
|
10
|
+
var paths = new string[]{ ${list} }; bool first = true;
|
|
11
|
+
foreach (var p in paths) {
|
|
12
|
+
if (!first) sb.Append(","); first = false;
|
|
13
|
+
var imp = UnityEditor.AssetImporter.GetAtPath(p) as UnityEditor.TextureImporter;
|
|
14
|
+
if (imp == null) { sb.Append("{\\"path\\":" + Q(p) + ",\\"missing\\":true}"); continue; }
|
|
15
|
+
var tex = UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.Texture2D>(p);
|
|
16
|
+
var s = imp.spritePivot;
|
|
17
|
+
sb.Append("{\\"path\\":" + Q(p) + ",\\"pixelsPerUnit\\":" + imp.spritePixelsPerUnit.ToString("R", System.Globalization.CultureInfo.InvariantCulture)
|
|
18
|
+
+ ",\\"filterMode\\":" + Q(imp.filterMode.ToString()) + ",\\"textureType\\":" + Q(imp.textureType.ToString()) + ",\\"maxTextureSize\\":" + imp.maxTextureSize
|
|
19
|
+
+ ",\\"spritePivot\\":[" + s.x.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "," + s.y.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + "]"
|
|
20
|
+
+ ",\\"mipmapEnabled\\":" + (imp.mipmapEnabled ? "true" : "false") + ",\\"width\\":" + (tex == null ? -1 : tex.width) + ",\\"height\\":" + (tex == null ? -1 : tex.height) + "}");
|
|
21
|
+
}
|
|
22
|
+
sb.Append("]"); return sb.ToString();
|
|
23
|
+
string Q(string v) { return "\\"" + v.Replace("\\\\", "\\\\\\\\").Replace("\\"", "\\\\\\"") + "\\""; }`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function compare(expected, actual) {
|
|
27
|
+
const mismatches = [];
|
|
28
|
+
for (const e of expected) {
|
|
29
|
+
const a = actual.find(x => x.path === e.path);
|
|
30
|
+
if (!a) { mismatches.push({ path: e.path, field: "path", expected: "present", actual: "not read" }); continue; }
|
|
31
|
+
if (a.missing) { mismatches.push({ path: e.path, field: "importer", expected: "TextureImporter", actual: "missing" }); continue; }
|
|
32
|
+
for (const f of FIELDS) {
|
|
33
|
+
if (!(f in e)) continue;
|
|
34
|
+
const ev = e[f], av = a[f];
|
|
35
|
+
const same = Array.isArray(ev) ? Array.isArray(av) && ev.length === av.length && ev.every((v, i) => Math.abs(v - av[i]) < 1e-4) : typeof ev === "number" ? Math.abs(ev - av) < 1e-4 : String(ev).toLowerCase() === String(av).toLowerCase();
|
|
36
|
+
if (!same) mismatches.push({ path: e.path, field: f, expected: ev, actual: av });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return mismatches;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function checkAssets(root, spec) {
|
|
43
|
+
const expected = spec.assets ?? [];
|
|
44
|
+
if (!expected.length) return { ok: false, errors: [{ message: "spec.assets is empty" }], assets: [], mismatches: [] };
|
|
45
|
+
const r = await evalCode(root, assetScript(expected.map(a => a.path)), 60);
|
|
46
|
+
const raw = r.result?.result ?? r.result;
|
|
47
|
+
if (!r.ok || typeof raw !== "string") return { ok: false, errors: r.errors?.length ? r.errors : [{ message: `unexpected eval result: ${String(JSON.stringify(raw)).slice(0, 200)}` }], assets: [], mismatches: [] };
|
|
48
|
+
let actual; try { actual = JSON.parse(raw); } catch (e) { return { ok: false, errors: [{ message: `cannot parse importer data: ${e.message}` }], assets: [], mismatches: [] }; }
|
|
49
|
+
const mismatches = compare(expected, actual);
|
|
50
|
+
return { ok: true, assets: actual, mismatches, errors: [] };
|
|
51
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Scripted playtest with telemetry, run entirely inside the Editor by one `script eval`.
|
|
2
|
+
// A spec says what to press when, which game members to sample and how often; the Editor writes a CSV
|
|
3
|
+
// of samples (10 Hz by default) and this module turns it into a measurement artifact with a summary
|
|
4
|
+
// (final values, first change of each boolean, min/max of each number, key presses). The playtester
|
|
5
|
+
// agent reads that artifact to judge feel; criteria can assert on the summary.
|
|
6
|
+
//
|
|
7
|
+
// spec = {
|
|
8
|
+
// name: "space-every-0.9s", durationS: 30, sampleMs: 100, holdS: 1,
|
|
9
|
+
// keys: [{ at: 0.9, key: "Space", holdMs: 60 }, ...] // explicit timeline, or
|
|
10
|
+
// pattern: { key: "Space", everyS: 0.9, fromS: 0.5, holdMs: 60 } // expanded into keys
|
|
11
|
+
// probes: [{ name: "distance", expr: "Runner.Instance.DistanceTravelled" }, { name: "alive", expr: "GameState.IsRunning" }],
|
|
12
|
+
// stopWhen: "!GameState.IsRunning", // optional C# expression, ends the run early
|
|
13
|
+
// reset: "UnityEngine.SceneManagement.SceneManager.LoadScene(UnityEngine.SceneManagement.SceneManager.GetActiveScene().name)",
|
|
14
|
+
// settleS: 0.5, // reset runs at schedule time; the timeline starts settleS later
|
|
15
|
+
// rules: [{ name: "jump-near-hazard", when: "<C# bool expr>", key: "Space", holdMs: 60, cooldownS: 0.3 }]
|
|
16
|
+
// // reactive input: pressed whenever `when` holds (a bot, not a metronome)
|
|
17
|
+
// }
|
|
18
|
+
// The schedule lands 2-3 s after Play (Pipeline round trips), so without `reset` the game is already
|
|
19
|
+
// mid-run; `gameTime` (Time.timeSinceLevelLoad) is sampled beside `t` to make that visible.
|
|
20
|
+
import fs from "node:fs";
|
|
21
|
+
import path from "node:path";
|
|
22
|
+
import { exists } from "../core/fs.mjs";
|
|
23
|
+
import { playStart, playStop, evalCode, waitReady, runInBackground, consoleGet } from "./unity.mjs";
|
|
24
|
+
import { sleep } from "../core/process-tree.mjs";
|
|
25
|
+
|
|
26
|
+
const q = v => JSON.stringify(String(v));
|
|
27
|
+
const INV = "System.Globalization.CultureInfo.InvariantCulture";
|
|
28
|
+
|
|
29
|
+
export function expandKeys(spec) {
|
|
30
|
+
if (Array.isArray(spec.keys) && spec.keys.length) return spec.keys.map(k => ({ at: Number(k.at), key: String(k.key), holdMs: Number(k.holdMs ?? 60) })).sort((a, b) => a.at - b.at);
|
|
31
|
+
const p = spec.pattern;
|
|
32
|
+
if (!p?.key || !(p.everyS > 0)) return [];
|
|
33
|
+
const keys = [];
|
|
34
|
+
for (let t = Number(p.fromS ?? 0.5); t < Number(spec.durationS ?? 30); t += Number(p.everyS)) keys.push({ at: Number(t.toFixed(3)), key: String(p.key), holdMs: Number(p.holdMs ?? 60) });
|
|
35
|
+
return keys;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function playtestScript(spec, { csvFile, doneFile }) {
|
|
39
|
+
const K = "UnityEngine.InputSystem";
|
|
40
|
+
const keys = expandKeys(spec);
|
|
41
|
+
const probes = spec.probes ?? [];
|
|
42
|
+
const sampleS = ((spec.sampleMs ?? 100) / 1000).toFixed(3);
|
|
43
|
+
const duration = Number(spec.durationS ?? 30).toFixed(3);
|
|
44
|
+
// each probe: value or "ERR" (the game may not have the object yet)
|
|
45
|
+
const probeCode = probes.map(p => `try { var v = (object)(${p.expr}); cells.Add(v is float f ? f.ToString("R", ${INV}) : v is double d ? d.ToString("R", ${INV}) : System.Convert.ToString(v, ${INV})); } catch (System.Exception) { cells.Add("ERR"); }`).join(" ");
|
|
46
|
+
const keyTimes = keys.map(k => `new double[]{${k.at.toFixed(3)}, ${(k.at + k.holdMs / 1000).toFixed(3)}}`).join(", ");
|
|
47
|
+
const keyNames = keys.map(k => `${K}.Key.${k.key}`).join(", ");
|
|
48
|
+
const stopWhen = spec.stopWhen ? `try { if (${spec.stopWhen}) stop = true; } catch (System.Exception) {}` : "";
|
|
49
|
+
const rules = (spec.rules ?? []).map((r, i) => `try { if (ruleDown[${i}] < 0 && dt >= ruleNext[${i}] && (${r.when})) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState(${K}.Key.${r.key ?? "Space"})); ruleDown[${i}] = dt; ruleHits[${i}]++; presses++; } else if (ruleDown[${i}] >= 0 && dt >= ruleDown[${i}] + ${((r.holdMs ?? 60) / 1000).toFixed(3)}) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); ruleDown[${i}] = -1; ruleNext[${i}] = dt + ${Number(r.cooldownS ?? 0.3).toFixed(3)}; } } catch (System.Exception) {}`).join(" ");
|
|
50
|
+
const ruleCount = (spec.rules ?? []).length;
|
|
51
|
+
const reset = spec.reset ? `try { ${spec.reset}; } catch (System.Exception e) { return "reset failed: " + e.Message; }` : "";
|
|
52
|
+
const settle = Number(spec.reset ? (spec.settleS ?? 0.5) : 0).toFixed(3);
|
|
53
|
+
return `var kb = ${K}.Keyboard.current; if (kb == null) return "no keyboard";
|
|
54
|
+
${reset}
|
|
55
|
+
var header = "t,gameTime,${probes.map(p => p.name).join(",")}";
|
|
56
|
+
var lines = new System.Collections.Generic.List<string>(); lines.Add(header);
|
|
57
|
+
var keyTimes = new double[][]{ ${keyTimes} }; var keyNames = new ${K}.Key[]{ ${keyNames} };
|
|
58
|
+
var keyState = new int[keyTimes.Length]; int presses = 0;
|
|
59
|
+
var ruleDown = new double[${ruleCount}]; var ruleNext = new double[${ruleCount}]; var ruleHits = new int[${ruleCount}]; for (int i = 0; i < ruleDown.Length; i++) ruleDown[i] = -1;
|
|
60
|
+
double tSched = UnityEditor.EditorApplication.timeSinceStartup; double t0 = tSched + ${settle}; double nextSample = 0; bool stop = false; bool finished = false;
|
|
61
|
+
UnityEditor.EditorApplication.CallbackFunction cb = null;
|
|
62
|
+
cb = () => {
|
|
63
|
+
if (finished) return;
|
|
64
|
+
double dt = UnityEditor.EditorApplication.timeSinceStartup - t0;
|
|
65
|
+
if (dt < 0) return;
|
|
66
|
+
for (int i = 0; i < keyTimes.Length; i++) {
|
|
67
|
+
if (keyState[i] == 0 && dt >= keyTimes[i][0]) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState(keyNames[i])); keyState[i] = 1; presses++; }
|
|
68
|
+
else if (keyState[i] == 1 && dt >= keyTimes[i][1]) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); keyState[i] = 2; }
|
|
69
|
+
}
|
|
70
|
+
${rules}
|
|
71
|
+
if (dt >= nextSample) { nextSample += ${sampleS}; var cells = new System.Collections.Generic.List<string>(); cells.Add(dt.ToString("F3", ${INV})); cells.Add(UnityEngine.Time.timeSinceLevelLoad.ToString("F3", ${INV})); ${probeCode} lines.Add(string.Join(",", cells)); }
|
|
72
|
+
${stopWhen}
|
|
73
|
+
if (stop || dt >= ${duration}) {
|
|
74
|
+
{ var cells = new System.Collections.Generic.List<string>(); cells.Add(dt.ToString("F3", ${INV})); cells.Add(UnityEngine.Time.timeSinceLevelLoad.ToString("F3", ${INV})); ${probeCode} lines.Add(string.Join(",", cells)); }
|
|
75
|
+
finished = true; UnityEditor.EditorApplication.update -= cb;
|
|
76
|
+
System.IO.File.WriteAllLines(${q(csvFile)}, lines);
|
|
77
|
+
System.IO.File.WriteAllText(${q(doneFile)}, "presses=" + presses + ";stopped=" + stop + ";t=" + dt.ToString("F3", ${INV}) + ";rules=" + string.Join("|", ruleHits));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
UnityEditor.EditorApplication.update += cb; return "scheduled";`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function parseCsv(text) {
|
|
84
|
+
const [head, ...rows] = text.trim().split(/\r?\n/);
|
|
85
|
+
const names = head.split(",");
|
|
86
|
+
return rows.filter(Boolean).map(r => { const c = r.split(","); const o = {}; names.forEach((n, i) => { const v = c[i]; o[n] = v === "True" ? true : v === "False" ? false : v === "ERR" || v === undefined ? null : Number.isFinite(Number(v)) ? Number(v) : v; }); return o; });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function summarize(samples, probes) {
|
|
90
|
+
const summary = { samples: samples.length, durationS: samples.at(-1)?.t ?? 0, gameTimeStartS: samples[0]?.gameTime ?? null, gameTimeEndS: samples.at(-1)?.gameTime ?? null };
|
|
91
|
+
for (const p of probes) {
|
|
92
|
+
const vals = samples.map(s => s[p.name]).filter(v => v !== null && v !== undefined);
|
|
93
|
+
if (!vals.length) { summary[p.name] = null; continue; }
|
|
94
|
+
const last = vals.at(-1);
|
|
95
|
+
if (typeof last === "boolean") {
|
|
96
|
+
const first = samples.find(s => s[p.name] !== null && s[p.name] !== vals[0]);
|
|
97
|
+
summary[p.name] = { final: last, initial: vals[0], changedAtS: first ? first.t : null };
|
|
98
|
+
} else if (typeof last === "number") {
|
|
99
|
+
summary[p.name] = { final: last, min: Math.min(...vals), max: Math.max(...vals) };
|
|
100
|
+
} else summary[p.name] = { final: last };
|
|
101
|
+
}
|
|
102
|
+
return summary;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function waitFor(file, timeoutMs) {
|
|
106
|
+
const until = Date.now() + timeoutMs;
|
|
107
|
+
while (Date.now() < until) { if (exists(file)) return true; await sleep(300); }
|
|
108
|
+
return exists(file);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function playtest(root, spec, { outDir, holdMs = 1000 } = {}) {
|
|
112
|
+
const outcome = { name: spec.name ?? "playtest", enteredPlayMode: false, exitedPlayMode: false, keys: expandKeys(spec).length, errors: [], samples: [], summary: null, console: null };
|
|
113
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
114
|
+
const csvFile = path.resolve(outDir, `${outcome.name}.csv`), doneFile = path.resolve(outDir, `${outcome.name}.done`);
|
|
115
|
+
for (const f of [csvFile, doneFile]) try { fs.rmSync(f, { force: true }); } catch {}
|
|
116
|
+
const rib = await runInBackground(root, true);
|
|
117
|
+
const mark = await consoleGet(root, { tail: 1, level: "log" });
|
|
118
|
+
const since = mark.ok ? { since: mark.result?.cursor, since_session: mark.result?.session } : {};
|
|
119
|
+
const started = await playStart(root);
|
|
120
|
+
if (!started.ok) { outcome.errors.push(started.errors?.[0]?.message ?? "play start failed"); return outcome; }
|
|
121
|
+
outcome.enteredPlayMode = true;
|
|
122
|
+
await sleep(Number(spec.holdS ?? 1) * 1000 || holdMs);
|
|
123
|
+
const r = await evalCode(root, playtestScript(spec, { csvFile, doneFile }), 60);
|
|
124
|
+
const value = r.result?.result ?? r.result;
|
|
125
|
+
if (!r.ok || value !== "scheduled") outcome.errors.push(`playtest not scheduled: ${r.errors?.[0]?.message ?? JSON.stringify(value).slice(0, 300)}`);
|
|
126
|
+
else {
|
|
127
|
+
const ok = await waitFor(doneFile, (Number(spec.durationS ?? 30) + 15) * 1000);
|
|
128
|
+
if (!ok) outcome.errors.push("playtest did not finish in time");
|
|
129
|
+
else {
|
|
130
|
+
const done = Object.fromEntries(fs.readFileSync(doneFile, "utf8").split(";").map(kv => kv.split("=")));
|
|
131
|
+
outcome.presses = Number(done.presses); outcome.stoppedEarly = done.stopped === "True"; outcome.ranS = Number(done.t); outcome.stoppedAtS = done.stopped === "True" ? Number(done.t) : null;
|
|
132
|
+
outcome.rulePresses = (spec.rules ?? []).map((r, i) => ({ name: r.name ?? `rule${i}`, presses: Number((done.rules ?? "").split("|")[i] ?? 0) }));
|
|
133
|
+
outcome.samples = exists(csvFile) ? parseCsv(fs.readFileSync(csvFile, "utf8")) : [];
|
|
134
|
+
outcome.summary = summarize(outcome.samples, spec.probes ?? []);
|
|
135
|
+
outcome.csv = csvFile;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const con = await consoleGet(root, { tail: 300, level: "log", ...since }); // the game's own log lines ([Runner] jump, dead …) are part of the telemetry
|
|
139
|
+
if (con.ok) outcome.console = { counts: con.result?.counts ?? null, entries: (con.result?.entries ?? []).slice(-100).map(e => ({ time: e.time ?? e.timestamp ?? null, level: e.level ?? null, message: String(e.message ?? e.text ?? "").slice(0, 200) })) };
|
|
140
|
+
const stopped = await playStop(root);
|
|
141
|
+
outcome.exitedPlayMode = stopped.ok;
|
|
142
|
+
await waitReady(root, { timeoutMs: 10000 });
|
|
143
|
+
if (rib.ok) await runInBackground(root, false);
|
|
144
|
+
return outcome;
|
|
145
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
// Unity CLI + Pipeline wrapper. Every call goes through here so the rest of the
|
|
2
|
+
// runtime never spells a Pipeline command name: names are discovered at runtime
|
|
3
|
+
// (`unity command --detail compact`) and cached per package version.
|
|
4
|
+
//
|
|
5
|
+
// Verified on 2026-11-04 against Unity CLI 1.0.0-beta.14 and com.unity.pipeline 0.9.x
|
|
6
|
+
// with Editors 6000.3.17f1 and 6000.6.7f1 (see docs/EVIDENCE-VERIFICATION.md).
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import { run, sleep } from "../core/process-tree.mjs";
|
|
10
|
+
import { readJson, writeJson, exists } from "../core/fs.mjs";
|
|
11
|
+
import { at, editorVersion, pipelineVersion } from "../core/project.mjs";
|
|
12
|
+
import { diag } from "../core/diag.mjs";
|
|
13
|
+
|
|
14
|
+
export const UNITY_BIN = process.env.NCCGS_UNITY_BIN || "unity";
|
|
15
|
+
// Test seam: NCCGS_UNITY_ARGS prepends fixed arguments (used to point NCCGS_UNITY_BIN=node at a fake CLI).
|
|
16
|
+
const PREFIX = process.env.NCCGS_UNITY_ARGS ? process.env.NCCGS_UNITY_ARGS.split("|") : [];
|
|
17
|
+
export const unityArgv = args => [...PREFIX, ...args];
|
|
18
|
+
|
|
19
|
+
function parseEnvelope(result) {
|
|
20
|
+
const text = (result.stdout || "").replace(/^/, "").trim();
|
|
21
|
+
let json = null;
|
|
22
|
+
try { json = JSON.parse(text); } catch {}
|
|
23
|
+
return { ...result, json, text };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// `unity test` / `unity build` start a batch Editor that must not outlive the command (ownTree);
|
|
27
|
+
// `unity open` starts an Editor that must. Everything else is a short-lived CLI call.
|
|
28
|
+
export async function cli(args, { cwd, timeoutMs = 120000 } = {}) {
|
|
29
|
+
const ownTree = ["test", "build"].includes(args[0]);
|
|
30
|
+
const result = await run(UNITY_BIN, unityArgv([...args, "--format", "json"]), { cwd, timeoutMs, ownTree });
|
|
31
|
+
const env = parseEnvelope(result);
|
|
32
|
+
if (result.timedOut || result.error || (env.json && env.json.success === false) || (!env.json && result.code !== 0)) {
|
|
33
|
+
const i = args.indexOf("--project-path");
|
|
34
|
+
const shown = i >= 0 ? [...args.slice(0, i), ...args.slice(i + 2)] : args;
|
|
35
|
+
diag(i >= 0 ? args[i + 1] : (process.env.NCCGS_PROJECT_ROOT ?? process.cwd()), "unity.failed", { args: shown.map(a => String(a).slice(0, 200)), code: result.code, timedOut: result.timedOut, errors: env.json?.errors ?? null, stderr: result.stderr?.slice(-500) ?? null, jobObject: result.jobObject });
|
|
36
|
+
}
|
|
37
|
+
return env;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function cliVersion() {
|
|
41
|
+
const r = await run(UNITY_BIN, unityArgv(["--version"]), { timeoutMs: 30000 });
|
|
42
|
+
return r.code === 0 ? r.stdout.trim() : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---- Editor instances ------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export async function status() {
|
|
48
|
+
const r = await cli(["status"], { timeoutMs: 30000 });
|
|
49
|
+
return r.json?.data?.instances ?? [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function instanceFor(root) {
|
|
53
|
+
const want = path.resolve(root).toLowerCase();
|
|
54
|
+
return (await status()).find(i => path.resolve(i.projectPath || i.project || "").toLowerCase() === want) ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Poll until the project's Pipeline server reports `ready`. Entering Play Mode on 6.3
|
|
58
|
+
// reloads the domain and drops the server for ~3 s, so every post-playmode call waits here.
|
|
59
|
+
export async function waitReady(root, { timeoutMs = 15000, intervalMs = 1000 } = {}) {
|
|
60
|
+
const started = Date.now();
|
|
61
|
+
let last = null;
|
|
62
|
+
while (Date.now() - started < timeoutMs) {
|
|
63
|
+
last = await instanceFor(root);
|
|
64
|
+
if (last?.state === "ready") return last;
|
|
65
|
+
await sleep(intervalMs);
|
|
66
|
+
}
|
|
67
|
+
return last;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- Pipeline commands -----------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export async function command(root, name, params = {}, { timeoutMs = 120000, retries = 2 } = {}) {
|
|
73
|
+
const args = ["command", ...name.split(" ")];
|
|
74
|
+
for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null) args.push(`--${k}`, String(v)); }
|
|
75
|
+
args.push("--project-path", root);
|
|
76
|
+
let r;
|
|
77
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
78
|
+
r = await cli(args, { timeoutMs });
|
|
79
|
+
const code = r.json?.errors?.[0]?.code;
|
|
80
|
+
if (code !== "EDITOR_NOT_RUNNING" || attempt === retries) break;
|
|
81
|
+
await waitReady(root, { timeoutMs: 8000 });
|
|
82
|
+
}
|
|
83
|
+
const ok = r.json?.success === true;
|
|
84
|
+
return { ok, result: r.json?.data?.result, errors: r.json?.errors ?? [], raw: r.json, code: r.code, stderr: r.stderr };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Discover the command catalog; cached under .nccgs/cache keyed by pipeline version.
|
|
88
|
+
export async function catalog(root, { refresh = false } = {}) {
|
|
89
|
+
const version = pipelineVersion(root) || "unknown";
|
|
90
|
+
const file = path.join(at(root, "cache"), `pipeline-commands-${version.replace(/[^\w.-]/g, "_")}.json`);
|
|
91
|
+
if (!refresh && exists(file)) return readJson(file);
|
|
92
|
+
const r = await cli(["command", "--detail", "compact", "--limit", "500", "--project-path", root], { timeoutMs: 60000 });
|
|
93
|
+
const commands = r.json?.data?.commands ?? [];
|
|
94
|
+
if (!commands.length) return { version, commands: [], discoveredAt: null, error: r.json?.errors?.[0]?.message ?? "no commands" };
|
|
95
|
+
const value = { version, discoveredAt: new Date().toISOString(), commands: commands.map(c => ({ command: c.command, group: c.group, name: c.name, description: c.description })) };
|
|
96
|
+
writeJson(file, value);
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Resolve a capability to the real command name (handles renames between Pipeline versions).
|
|
101
|
+
const CAPABILITIES = {
|
|
102
|
+
playStart: [/^editor\.playmode start$/, /^editor_play$/],
|
|
103
|
+
playStop: [/^editor\.playmode stop$/, /^editor_stop$/],
|
|
104
|
+
screenshot: [/^capture screenshot$/, /^screenshot$/],
|
|
105
|
+
consoleGet: [/^console get$/, /^get_console$/],
|
|
106
|
+
consoleStatus: [/^console status$/],
|
|
107
|
+
consoleClear: [/^console clear$/],
|
|
108
|
+
eval: [/^script eval$/, /^eval$/],
|
|
109
|
+
testRun: [/^test run$/, /^run_tests$/],
|
|
110
|
+
testStatus: [/^test status$/, /^test_status$/],
|
|
111
|
+
runInBackground: [/^editor\.playmode set_run_in_background$/],
|
|
112
|
+
testList: [/^test list$/],
|
|
113
|
+
simulateKey: [/^runtime simulate_key$/],
|
|
114
|
+
performance: [/^diagnostics get_performance$/],
|
|
115
|
+
hierarchy: [/^scene get_hierarchy$/, /^get_scene_hierarchy$/],
|
|
116
|
+
recompile: [/^script recompile$/, /^recompile$/],
|
|
117
|
+
recompileStatus: [/^script recompile_status$/]
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export async function resolve(root, capability) {
|
|
121
|
+
const cat = await catalog(root);
|
|
122
|
+
const patterns = CAPABILITIES[capability];
|
|
123
|
+
if (!patterns) throw new Error(`Unknown capability ${capability}`);
|
|
124
|
+
const hit = cat.commands.find(c => patterns.some(p => p.test(c.command)));
|
|
125
|
+
return hit?.command ?? null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function call(root, capability, params, options) {
|
|
129
|
+
const name = await resolve(root, capability);
|
|
130
|
+
if (!name) return { ok: false, errors: [{ code: "CAPABILITY_MISSING", message: `No Pipeline command for ${capability}` }] };
|
|
131
|
+
return command(root, name, params, options);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---- High-level operations used by `task verify` -------------------------------
|
|
135
|
+
|
|
136
|
+
export async function playStart(root) {
|
|
137
|
+
const r = await call(root, "playStart", {});
|
|
138
|
+
if (r.ok) await waitReady(root, { timeoutMs: 15000 });
|
|
139
|
+
return r;
|
|
140
|
+
}
|
|
141
|
+
export const playStop = root => call(root, "playStop", {});
|
|
142
|
+
// Play Mode freezes when the Editor loses focus unless Run In Background is on; the Pipeline override
|
|
143
|
+
// restores the project's own value when disabled. Best effort: an older Pipeline without it is fine.
|
|
144
|
+
export const runInBackground = (root, enable) => call(root, "runInBackground", { enable }).catch(() => ({ ok: false }));
|
|
145
|
+
export const simulateKey = (root, key, action = "press") => call(root, "simulateKey", { key, action });
|
|
146
|
+
export const evalCode = (root, code, timeout = 30) => call(root, "eval", { code, timeout });
|
|
147
|
+
export const performance = root => call(root, "performance", {});
|
|
148
|
+
export const hierarchy = root => call(root, "hierarchy", {});
|
|
149
|
+
|
|
150
|
+
export async function screenshot(root, output, { view = "game", width = 1280, height = 720 } = {}) {
|
|
151
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
152
|
+
const r = await call(root, "screenshot", { view, output, width, height });
|
|
153
|
+
return { ...r, path: r.ok ? (r.result?.path ?? output) : null, bytes: exists(output) ? fs.statSync(output).size : 0 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const consoleStatus = root => call(root, "consoleStatus", {});
|
|
157
|
+
export const consoleGet = (root, { tail = 100, level = "warn", since, since_session } = {}) =>
|
|
158
|
+
call(root, "consoleGet", { tail, level, since, since_session });
|
|
159
|
+
|
|
160
|
+
// Tests inside the live Editor. Returns the Pipeline summary normalised to tests.json shape.
|
|
161
|
+
// The command envelope can succeed while the run itself failed (result.success false + result.error);
|
|
162
|
+
// that is a failed run, never "ok with 0 tests".
|
|
163
|
+
export async function testRunLive(root, { mode = "editor", filter, timeout = 600 } = {}) {
|
|
164
|
+
if (mode === "playmode") return testRunLiveAsync(root, { mode, filter, timeout });
|
|
165
|
+
const r = await call(root, "testRun", { mode, filter: filter || undefined, timeout }, { timeoutMs: (timeout + 60) * 1000 });
|
|
166
|
+
if (!r.ok) return { ok: false, errors: r.errors };
|
|
167
|
+
if (r.result?.success === false) return runFailed(mode, r.result.error ?? r.result.message);
|
|
168
|
+
const s = r.result?.Summary ?? {};
|
|
169
|
+
const results = r.result?.Results ?? [];
|
|
170
|
+
return {
|
|
171
|
+
ok: true,
|
|
172
|
+
source: "pipeline",
|
|
173
|
+
mode: r.result?.Mode ?? mode,
|
|
174
|
+
total: s.Total ?? 0, passed: s.Passed ?? 0, failed: s.Failed ?? 0, skipped: s.Skipped ?? 0, inconclusive: s.Inconclusive ?? 0,
|
|
175
|
+
durationSec: r.result?.Duration ?? null,
|
|
176
|
+
failures: results.filter(t => /fail/i.test(t.Result ?? t.Outcome ?? t.Status ?? "")).map(t => ({ name: t.Name ?? t.FullName, message: t.Message ?? "" }))
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// PlayMode cannot run synchronously over HTTP: entering Play Mode reloads the domain and drops the request.
|
|
181
|
+
// Start it async and poll `test status`. Starting a run deletes the previous status file, so a
|
|
182
|
+
// "completed" status read after a successful start belongs to this run.
|
|
183
|
+
async function testRunLiveAsync(root, { mode, filter, timeout }) {
|
|
184
|
+
const started = await call(root, "testRun", { mode, filter: filter || undefined, async_tests: true, timeout });
|
|
185
|
+
if (!started.ok) return { ok: false, source: "pipeline", mode, errors: started.errors };
|
|
186
|
+
if (started.result?.success === false) return runFailed(mode, started.result.error ?? started.result.message);
|
|
187
|
+
const deadline = Date.now() + (timeout + 60) * 1000;
|
|
188
|
+
let st = null;
|
|
189
|
+
while (Date.now() < deadline) {
|
|
190
|
+
await sleep(2000);
|
|
191
|
+
const r = await call(root, "testStatus", {});
|
|
192
|
+
st = r.ok ? r.result : null; // null while the domain reloads or before the request is registered
|
|
193
|
+
if (st && st.status !== "running") break;
|
|
194
|
+
}
|
|
195
|
+
if (st?.status !== "completed") return runFailed(mode, st?.message ?? `test status: ${st?.status ?? "timed out"}`);
|
|
196
|
+
const s = st.summary ?? {};
|
|
197
|
+
const results = st.results ?? [];
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
source: "pipeline",
|
|
201
|
+
mode: "PlayMode",
|
|
202
|
+
total: s.total ?? 0, passed: s.passed ?? 0, failed: s.failed ?? 0, skipped: s.skipped ?? 0, inconclusive: s.inconclusive ?? 0,
|
|
203
|
+
durationSec: st.duration ?? null,
|
|
204
|
+
failures: results.filter(t => /fail/i.test(t.Status ?? "")).map(t => ({ name: t.FullName ?? t.Name, message: t.Message ?? "" }))
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function runFailed(mode, message) {
|
|
209
|
+
return { ok: false, source: "pipeline", mode, errors: [{ code: "TEST_RUN_FAILED", message: message ?? "test run failed" }] };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Tests through a batch Editor (no live Editor on this project). Pins the project's own Editor version.
|
|
213
|
+
export async function testRunBatch(root, { mode = "EditMode", filter, timeout = 900, output } = {}) {
|
|
214
|
+
const version = editorVersion(root);
|
|
215
|
+
const xml = output || path.join(at(root, "measurements"), `${mode.toLowerCase()}-results.xml`);
|
|
216
|
+
const args = ["test", root, "--mode", mode, "--output", xml, "--timeout", String(timeout)];
|
|
217
|
+
if (filter) args.push("--filter", filter);
|
|
218
|
+
if (version) args.push("--editor-version", version);
|
|
219
|
+
const r = await cli(args, { timeoutMs: (timeout + 120) * 1000 });
|
|
220
|
+
const errorCode = r.json?.errors?.[0]?.code ?? null;
|
|
221
|
+
if (!exists(xml)) return { ok: false, source: "batch", exitCode: r.code, errorCode, errors: r.json?.errors ?? [{ message: r.stderr.slice(0, 400) }] };
|
|
222
|
+
return { ok: true, source: "batch", exitCode: r.code, mode, xml, ...parseNUnit(fs.readFileSync(xml, "utf8")) };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Minimal NUnit3 reader: run-level counts plus failed test cases. No XML dependency.
|
|
226
|
+
export function parseNUnit(text) {
|
|
227
|
+
const attr = (tag, name) => Number(text.match(new RegExp(`<${tag}[^>]*\\b${name}="(\\d+)"`))?.[1] ?? 0);
|
|
228
|
+
const failures = [];
|
|
229
|
+
for (const m of text.matchAll(/<test-case\b([^>]*)result="Failed"([^>]*)>([\s\S]*?)<\/test-case>/g)) {
|
|
230
|
+
const head = m[1] + m[2];
|
|
231
|
+
const name = head.match(/fullname="([^"]*)"/)?.[1] ?? head.match(/name="([^"]*)"/)?.[1] ?? "unknown";
|
|
232
|
+
const message = m[3].match(/<message><!\[CDATA\[([\s\S]*?)\]\]><\/message>/)?.[1]?.trim() ?? "";
|
|
233
|
+
failures.push({ name, message: message.slice(0, 500) });
|
|
234
|
+
}
|
|
235
|
+
return { total: attr("test-run", "total"), passed: attr("test-run", "passed"), failed: attr("test-run", "failed"), skipped: attr("test-run", "skipped"), inconclusive: attr("test-run", "inconclusive"), durationSec: Number(text.match(/<test-run[^>]*duration="([\d.]+)"/)?.[1] ?? 0), failures };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Pick live when the project has a ready Editor, batch otherwise. A run that executed no test is not ok:
|
|
239
|
+
// it means a wrong mode/filter or a run that never started, and must not read as a green 0/0.
|
|
240
|
+
export async function testRun(root, options = {}) {
|
|
241
|
+
const live = await instanceFor(root);
|
|
242
|
+
const r = live?.state === "ready"
|
|
243
|
+
? await testRunLive(root, { mode: options.mode === "PlayMode" ? "playmode" : options.mode === "EditMode" ? "editor" : (options.mode ?? "editor"), filter: options.filter, timeout: options.timeout })
|
|
244
|
+
: await testRunBatch(root, { mode: options.mode === "playmode" ? "PlayMode" : options.mode === "editor" ? "EditMode" : (options.mode ?? "EditMode"), filter: options.filter, timeout: options.timeout, output: options.output });
|
|
245
|
+
if (r.ok && (r.total ?? 0) === 0) return { ...r, ok: false, errors: [...(r.errors ?? []), { code: "NO_TESTS_RAN", message: `no ${r.mode ?? "test"} tests ran${options.filter ? ` for filter '${options.filter}'` : ""}` }] };
|
|
246
|
+
return r;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Observe: enter Play Mode, wait, optionally press keys, capture screenshots, read the console, exit.
|
|
250
|
+
// Keys are pressed once, after the first hold and before frame 1 (so frame 1 shows the reaction and frame 2
|
|
251
|
+
// the settled state); `keysEveryFrame` repeats them before every frame. `matchLog` (regex) counts the
|
|
252
|
+
// Play Mode log entries that match it, so a criterion can assert "one log line per press".
|
|
253
|
+
// Timed mode (`shotsMs` given): one `script eval` schedules everything inside the Editor on
|
|
254
|
+
// EditorApplication.update — key down at t=0 through the Input System's low-level queue, key up after
|
|
255
|
+
// `holdKeyMs`, ScreenCapture.CaptureScreenshot at each offset in `shotsMs`. Every Pipeline round trip costs
|
|
256
|
+
// ~0.9 s, so this is the only way to photograph anything that happens within a second of a key press
|
|
257
|
+
// (a 0.75 s jump landed before the first `capture screenshot` ever returned). Frames are the Game view's
|
|
258
|
+
// own size. Needs com.unity.inputsystem; with no keyboard device the eval returns "no keyboard".
|
|
259
|
+
const q = v => JSON.stringify(String(v));
|
|
260
|
+
function timedScript({ keys, holdKeyMs, shots, timingFile }) {
|
|
261
|
+
const K = "UnityEngine.InputSystem";
|
|
262
|
+
const keyList = keys.map(k => `${K}.Key.${k}`).join(", ");
|
|
263
|
+
const press = keys.length ? `${K}.InputSystem.QueueStateEvent(kb, new ${K}.LowLevel.KeyboardState(${keyList}));` : "";
|
|
264
|
+
const release = keys.length ? `if (!released && dt > ${(holdKeyMs / 1000).toFixed(3)}) { ${K}.InputSystem.QueueStateEvent(${K}.Keyboard.current, new ${K}.LowLevel.KeyboardState()); released = true; }` : "";
|
|
265
|
+
const captures = shots.map((sh, i) => `if (!done[${i}] && dt > ${(sh.ms / 1000).toFixed(3)}) { UnityEngine.ScreenCapture.CaptureScreenshot(${q(sh.path)}); done[${i}] = true; gameTimes[${i}] = UnityEngine.Time.timeSinceLevelLoad; }`).join(" ");
|
|
266
|
+
const last = ((shots.at(-1)?.ms ?? 0) / 1000 + 0.5).toFixed(3);
|
|
267
|
+
const timing = `System.IO.File.WriteAllText(${q(timingFile)}, g0.ToString("R", System.Globalization.CultureInfo.InvariantCulture) + ";" + string.Join(",", System.Array.ConvertAll(gameTimes, x => x.ToString("R", System.Globalization.CultureInfo.InvariantCulture))));`;
|
|
268
|
+
return `var kb = ${K}.Keyboard.current; if (kb == null) return "no keyboard"; ${press} double t0 = UnityEditor.EditorApplication.timeSinceStartup; float g0 = UnityEngine.Time.timeSinceLevelLoad; bool released = false; var done = new bool[${shots.length}]; var gameTimes = new float[${shots.length}]; UnityEditor.EditorApplication.CallbackFunction cb = null; cb = () => { double dt = UnityEditor.EditorApplication.timeSinceStartup - t0; ${release} ${captures} if (dt > ${last}) { UnityEditor.EditorApplication.update -= cb; ${timing} } }; UnityEditor.EditorApplication.update += cb; return "scheduled";`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseTiming(text) {
|
|
272
|
+
const [g0, rest = ""] = text.trim().split(";");
|
|
273
|
+
const n = v => { const x = Number(v); return Number.isFinite(x) ? x : null; };
|
|
274
|
+
return { scheduledAtGameTime: n(g0), captureGameTimes: rest.split(",").map(n) };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function waitForFiles(files, timeoutMs) {
|
|
278
|
+
const deadline = Date.now() + timeoutMs;
|
|
279
|
+
const sizes = {};
|
|
280
|
+
while (Date.now() < deadline) {
|
|
281
|
+
let ready = true;
|
|
282
|
+
for (const f of files) {
|
|
283
|
+
const size = exists(f) ? fs.statSync(f).size : 0;
|
|
284
|
+
if (!size || sizes[f] !== size) ready = false;
|
|
285
|
+
sizes[f] = size;
|
|
286
|
+
}
|
|
287
|
+
if (ready) return true;
|
|
288
|
+
await sleep(300);
|
|
289
|
+
}
|
|
290
|
+
return files.every(f => exists(f) && fs.statSync(f).size > 0);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export async function observe(root, { screensDir, frames = 2, keys = [], holdMs = 1500, width = 1280, height = 720, keysEveryFrame = false, matchLog = null, shotsMs = null, holdKeyMs = 60 } = {}) {
|
|
294
|
+
const outcome = { enteredPlayMode: false, exitedPlayMode: false, screens: [], keys: [], console: null, performance: null, errors: [], logMatches: null, timed: Array.isArray(shotsMs) && shotsMs.length > 0 };
|
|
295
|
+
const rib = await runInBackground(root, true);
|
|
296
|
+
outcome.runInBackground = rib.ok === true;
|
|
297
|
+
const mark = await consoleGet(root, { tail: 1, level: "log" }); // cursor: only entries after this count
|
|
298
|
+
const since = mark.ok ? { since: mark.result?.cursor, since_session: mark.result?.session } : {};
|
|
299
|
+
const started = await playStart(root);
|
|
300
|
+
if (!started.ok) { outcome.errors.push(started.errors?.[0]?.message ?? "play start failed"); return outcome; }
|
|
301
|
+
const playStartedAt = Date.now();
|
|
302
|
+
outcome.enteredPlayMode = true;
|
|
303
|
+
await sleep(holdMs);
|
|
304
|
+
if (outcome.timed) {
|
|
305
|
+
fs.mkdirSync(screensDir, { recursive: true });
|
|
306
|
+
const shots = shotsMs.map((ms, i) => ({ ms, path: path.resolve(screensDir, `${String(i + 1).padStart(2, "0")}.png`) }));
|
|
307
|
+
for (const sh of shots) { try { fs.rmSync(sh.path, { force: true }); } catch {} }
|
|
308
|
+
const timingFile = path.resolve(screensDir, "timing.json");
|
|
309
|
+
try { fs.rmSync(timingFile, { force: true }); } catch {}
|
|
310
|
+
const scheduledAt = Date.now();
|
|
311
|
+
const r = await evalCode(root, timedScript({ keys, holdKeyMs, shots, timingFile }));
|
|
312
|
+
outcome.scheduleDelayMs = Date.now() - playStartedAt; // wall-clock from Play start to the schedule: frames are atMs after this
|
|
313
|
+
const value = r.result?.result ?? r.result;
|
|
314
|
+
if (!r.ok || value !== "scheduled") outcome.errors.push(`timed observe not scheduled: ${r.errors?.[0]?.message ?? JSON.stringify(value).slice(0, 200)}`);
|
|
315
|
+
else {
|
|
316
|
+
for (const key of keys) outcome.keys.push({ key, ok: true, holdMs: holdKeyMs });
|
|
317
|
+
const ok = await waitForFiles([...shots.map(s => s.path), timingFile], (shots.at(-1).ms) + 8000);
|
|
318
|
+
const timing = exists(timingFile) ? parseTiming(fs.readFileSync(timingFile, "utf8")) : null;
|
|
319
|
+
outcome.scheduledAtGameTime = timing?.scheduledAtGameTime ?? null;
|
|
320
|
+
for (const [i, sh] of shots.entries()) outcome.screens.push({ path: exists(sh.path) ? sh.path : null, bytes: exists(sh.path) ? fs.statSync(sh.path).size : 0, ok: exists(sh.path), atMs: sh.ms, gameTimeS: timing?.captureGameTimes?.[i] ?? null });
|
|
321
|
+
if (!ok) outcome.errors.push("timed observe: not every screenshot was written");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
for (let i = 0; !outcome.timed && i < Math.max(1, frames); i++) {
|
|
325
|
+
if (i === 0 || keysEveryFrame) {
|
|
326
|
+
for (const key of keys) { const k = await simulateKey(root, key); outcome.keys.push({ key, ok: k.ok }); }
|
|
327
|
+
if (keys.length) await sleep(400);
|
|
328
|
+
}
|
|
329
|
+
const file = path.join(screensDir, `${String(i + 1).padStart(2, "0")}.png`);
|
|
330
|
+
const shot = await screenshot(root, file, { width, height });
|
|
331
|
+
outcome.screens.push({ path: shot.path, bytes: shot.bytes, ok: shot.ok });
|
|
332
|
+
if (!shot.ok) outcome.errors.push(shot.errors?.[0]?.message ?? "screenshot failed");
|
|
333
|
+
if (i + 1 < frames) await sleep(holdMs);
|
|
334
|
+
}
|
|
335
|
+
const perf = await performance(root);
|
|
336
|
+
if (perf.ok) outcome.performance = perf.result;
|
|
337
|
+
const con = await consoleGet(root, { tail: 50, level: "warn" });
|
|
338
|
+
if (con.ok) outcome.console = { counts: con.result?.counts ?? null, groundTruth: con.result?.groundTruth ?? null, entries: (con.result?.entries ?? []).slice(-20) };
|
|
339
|
+
if (matchLog) {
|
|
340
|
+
const all = await consoleGet(root, { tail: 500, level: "log", ...since });
|
|
341
|
+
const re = new RegExp(matchLog);
|
|
342
|
+
outcome.logMatches = all.ok ? (all.result?.entries ?? []).filter(e => re.test(e.message ?? e.text ?? "")).length : null;
|
|
343
|
+
}
|
|
344
|
+
const stopped = await playStop(root);
|
|
345
|
+
outcome.exitedPlayMode = stopped.ok;
|
|
346
|
+
if (!stopped.ok) outcome.errors.push(stopped.errors?.[0]?.message ?? "play stop failed");
|
|
347
|
+
await waitReady(root, { timeoutMs: 10000 });
|
|
348
|
+
if (outcome.runInBackground) await runInBackground(root, false);
|
|
349
|
+
return outcome;
|
|
350
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Windows only: prove the Job Object kills a re-parented grandchild that taskkill /T would miss.
|
|
3
|
+
// Spawns powershell -> Start-Process ping (detached), terminates the tree, counts ping.exe before/after.
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { run, terminate } from "../runtime/core/process-tree.mjs";
|
|
6
|
+
import { available } from "../runtime/core/job-object.mjs";
|
|
7
|
+
const count = () => (spawnSync("tasklist", ["/FI", "IMAGENAME eq ping.exe", "/NH"], { encoding: "utf8" }).stdout.match(/ping\.exe/gi) || []).length;
|
|
8
|
+
const before = count();
|
|
9
|
+
const jobAvailable = await available();
|
|
10
|
+
const started = run("powershell", ["-NoProfile", "-Command", "Start-Process ping -ArgumentList '-n','40','127.0.0.1' -WindowStyle Hidden; Start-Sleep 40"], { timeoutMs: 6000 });
|
|
11
|
+
await new Promise(r => setTimeout(r, 3500));
|
|
12
|
+
const during = count();
|
|
13
|
+
const result = await started; // the timeout fires terminate() with the job
|
|
14
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
15
|
+
const after = count();
|
|
16
|
+
const out = { jobAvailable, jobObject: result.jobObject, timedOut: result.timedOut, pingBefore: before, pingDuring: during, pingAfter: after, grandchildKilled: during > before && after <= before };
|
|
17
|
+
console.log(JSON.stringify(out, null, 2));
|
|
18
|
+
process.exit(out.grandchildKilled ? 0 : 1);
|