@vgai/editor 0.2.0
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/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- package/vite-plugin-ui-oid.ts +561 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vgai doctor` — data-asset checks (docs/DATA-TOOLS-DESIGN.md §6.6, the
|
|
3
|
+
* enforcement half of W5). PURE data-in/data-out, exactly like its sibling
|
|
4
|
+
* `report.ts`: the fs/schema-executing scraping lives in
|
|
5
|
+
* `packages/editor/scripts/doctor-data-scan.ts` (spawned under `tsx`, which
|
|
6
|
+
* is what lets it dynamic-import the project's `.schema.ts` modules), and
|
|
7
|
+
* hands this module plain {@link ScannedDataProject} data — so the check
|
|
8
|
+
* RULES are unit-testable without a filesystem or a project.
|
|
9
|
+
*
|
|
10
|
+
* The checks, severities fixed by the spec (§6.6):
|
|
11
|
+
*
|
|
12
|
+
* - **data file fails schema** → ERROR (flips the doctor exit code). Also
|
|
13
|
+
* covers a `.data.json` that isn't valid JSON at all.
|
|
14
|
+
* - **dangling `file#key` ref** → ERROR. Shared definition with the
|
|
15
|
+
* build-path plugin via the engine's `findDanglingDataRefs` (a ref only
|
|
16
|
+
* "exists" once its file prefix names a real data asset — see
|
|
17
|
+
* `data-check-core.ts`'s calibration note).
|
|
18
|
+
* - **declared `dataRef(target)` field's target file missing**
|
|
19
|
+
* (`'data-ref-missing-file'`) → ERROR — the §9.4 fix. Unlike the
|
|
20
|
+
* conventional-string check above, this fires purely from the SCHEMA's
|
|
21
|
+
* declared intent (`"x-vgai-ref": target` on the emitted field), so
|
|
22
|
+
* renaming/deleting the target file no longer silently un-checks the
|
|
23
|
+
* ref — see `collectDeclaredRefFields`/`findMissingRefTargets` in
|
|
24
|
+
* `data-check-core.ts`.
|
|
25
|
+
* - **`.data.json` without a schema** → WARN. Two shapes: no governing
|
|
26
|
+
* `.schema.ts` at all, or a `.schema.ts` whose emitted `.schema.json`
|
|
27
|
+
* was never generated (D4 default is generated-and-committed).
|
|
28
|
+
* - **zero data assets in a non-trivial project** → INFO nudge (the
|
|
29
|
+
* doctrine's "deleting the pattern is a choice" — §6.2).
|
|
30
|
+
*
|
|
31
|
+
* Anti-shim doctrine (same as report.ts): a schema that could not be
|
|
32
|
+
* EXECUTED (import failed, ambiguous exports) is a WARN naming the reason —
|
|
33
|
+
* never silently counted as validated, never fabricated into an error the
|
|
34
|
+
* data file didn't earn.
|
|
35
|
+
*
|
|
36
|
+
* NOTE the engine import below is RELATIVE, not `@engine/*`: this module is
|
|
37
|
+
* imported by `scripts/run-doctor.ts`, which runs under plain `tsx` where no
|
|
38
|
+
* tsconfig-path alias exists (same constraint documented at run-doctor's own
|
|
39
|
+
* imports).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import {
|
|
43
|
+
collectDeclaredRefFields,
|
|
44
|
+
findDanglingDataRefs,
|
|
45
|
+
findMissingRefTargets,
|
|
46
|
+
} from '../../../engine/src/data/data-check-core';
|
|
47
|
+
|
|
48
|
+
export type DataFindingSeverity = 'error' | 'warn' | 'info';
|
|
49
|
+
|
|
50
|
+
export type DataCheckId =
|
|
51
|
+
| 'data-schema' // §6.6 check 1: data file fails schema (error)
|
|
52
|
+
| 'data-dangling-ref' // §6.6 check 2: dangling file#key ref (error)
|
|
53
|
+
| 'data-missing-schema' // §6.6 check 3: .data.json without schema (warn)
|
|
54
|
+
| 'data-duplicate-stem' // duplicate asset stem across files (error)
|
|
55
|
+
| 'data-ref-missing-file' // §9.4 fix: a declared dataRef(target)'s target file doesn't exist (error)
|
|
56
|
+
| 'data-none'; // §6.6 check 4: zero data assets, non-trivial project (info)
|
|
57
|
+
|
|
58
|
+
export interface DataDoctorFinding {
|
|
59
|
+
readonly severity: DataFindingSeverity;
|
|
60
|
+
readonly check: DataCheckId;
|
|
61
|
+
/** Project-relative path of the offending file, or `null` for project-level findings. */
|
|
62
|
+
readonly file: string | null;
|
|
63
|
+
readonly message: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Outcome of executing an asset's `.schema.ts` against its `.data.json` — scraped by doctor-data-scan.ts. */
|
|
67
|
+
export type ScannedSchemaValidation =
|
|
68
|
+
| { readonly kind: 'ok' }
|
|
69
|
+
| { readonly kind: 'invalid'; readonly message: string }
|
|
70
|
+
/** The schema module could not be executed/identified — reason says why (import error, ambiguous exports). */
|
|
71
|
+
| { readonly kind: 'unavailable'; readonly reason: string };
|
|
72
|
+
|
|
73
|
+
export interface ScannedDataAsset {
|
|
74
|
+
/** Asset stem — filename minus `.data.json` (the name refs address it by, §2.2). */
|
|
75
|
+
readonly name: string;
|
|
76
|
+
/** Project-relative path of the `.data.json`. */
|
|
77
|
+
readonly dataPath: string;
|
|
78
|
+
/** Parsed JSON payload, or `undefined` when `jsonError` is set. */
|
|
79
|
+
readonly json?: unknown;
|
|
80
|
+
/** JSON.parse failure message, or `null` if the file parsed. */
|
|
81
|
+
readonly jsonError: string | null;
|
|
82
|
+
/** Project-relative path of the sibling `.schema.ts`, or `null` if none exists. */
|
|
83
|
+
readonly schemaTsPath: string | null;
|
|
84
|
+
/** Project-relative path of the sibling EMITTED `.schema.json`, or `null` if none exists. */
|
|
85
|
+
readonly schemaJsonPath: string | null;
|
|
86
|
+
/** Validation outcome — `null` iff there is no `.schema.ts` to validate against. */
|
|
87
|
+
readonly validation: ScannedSchemaValidation | null;
|
|
88
|
+
/**
|
|
89
|
+
* The emitted JSON Schema (`toDataJsonSchema` output, or the committed
|
|
90
|
+
* `.schema.json` twin when the module couldn't be executed) — present
|
|
91
|
+
* whenever EITHER is available, independent of `validation`'s outcome.
|
|
92
|
+
* Used only to find declared `dataRef(target)` fields (§9.4 fix); `null`/
|
|
93
|
+
* absent degrades to "no declared refs found here", never an error.
|
|
94
|
+
*/
|
|
95
|
+
readonly emittedSchema?: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface ScannedDataProject {
|
|
99
|
+
readonly assets: readonly ScannedDataAsset[];
|
|
100
|
+
/** Count of `.ts`/`.tsx` files under the project's `src/` — the non-triviality signal. */
|
|
101
|
+
readonly sourceFileCount: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* "Non-trivial project" calibration for the §6.6 info nudge: the template
|
|
106
|
+
* scaffolds ~17 source files INCLUDING the data trio, so a project that
|
|
107
|
+
* deleted the pattern still gets nudged, while a three-file experiment does
|
|
108
|
+
* not. Deliberately a soft signal — it gates an INFO, nothing stronger.
|
|
109
|
+
*/
|
|
110
|
+
export const NON_TRIVIAL_SOURCE_FILE_COUNT = 10;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Every `.data.json`'s asset "name" is its STEM (filename minus the
|
|
114
|
+
* extension, §2.2) — the same identity `file#key` refs address a file by.
|
|
115
|
+
* `src/data/` is walked recursively (`scanProjectDataAssets`), so two files
|
|
116
|
+
* in different subdirectories can share a stem (e.g. `src/data/enemies` and
|
|
117
|
+
* `src/data/legacy/enemies`) — an unresolvable identity collision that the
|
|
118
|
+
* downstream stem-keyed `Map` (ref resolution, below) would otherwise
|
|
119
|
+
* silently paper over with last-wins, while `.find()`-based error attribution
|
|
120
|
+
* elsewhere would report the FIRST one — two different silent tie-breaks
|
|
121
|
+
* disagreeing with each other. This must be a hard error naming every
|
|
122
|
+
* colliding path, not a silent collision.
|
|
123
|
+
*/
|
|
124
|
+
function findDuplicateStems(assets: readonly ScannedDataAsset[]): Map<string, ScannedDataAsset[]> {
|
|
125
|
+
const byStem = new Map<string, ScannedDataAsset[]>();
|
|
126
|
+
for (const asset of assets) {
|
|
127
|
+
const group = byStem.get(asset.name);
|
|
128
|
+
if (group) group.push(asset);
|
|
129
|
+
else byStem.set(asset.name, [asset]);
|
|
130
|
+
}
|
|
131
|
+
for (const [stem, group] of [...byStem]) {
|
|
132
|
+
if (group.length < 2) byStem.delete(stem);
|
|
133
|
+
}
|
|
134
|
+
return byStem;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Assemble the §6.6 findings from a scanned project. Deterministic: per-asset checks in scan order, then refs, then the project-level nudge. */
|
|
138
|
+
export function assembleDataFindings(scanned: ScannedDataProject): DataDoctorFinding[] {
|
|
139
|
+
const findings: DataDoctorFinding[] = [];
|
|
140
|
+
|
|
141
|
+
for (const asset of scanned.assets) {
|
|
142
|
+
findings.push(...perAssetFindings(asset));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const duplicateStems = findDuplicateStems(scanned.assets);
|
|
146
|
+
for (const [stem, group] of duplicateStems) {
|
|
147
|
+
const paths = group.map((a) => a.dataPath).join(', ');
|
|
148
|
+
for (const asset of group) {
|
|
149
|
+
findings.push({
|
|
150
|
+
severity: 'error',
|
|
151
|
+
check: 'data-duplicate-stem',
|
|
152
|
+
file: asset.dataPath,
|
|
153
|
+
message:
|
|
154
|
+
`asset stem "${stem}" is claimed by ${group.length} files (${paths}) — "file#key" refs ` +
|
|
155
|
+
'address a data asset by its stem alone (§2.2), so this is an unresolvable identity ' +
|
|
156
|
+
'collision. Rename one; asset stems must be unique across the whole project, not just ' +
|
|
157
|
+
'per directory.',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Dangling refs — resolved across every file that parsed (an unparseable
|
|
163
|
+
// file is already an error above and cannot be resolved against). A
|
|
164
|
+
// duplicate-stem asset is excluded here too: it already has its own hard
|
|
165
|
+
// error above, and resolving refs against "whichever file happened to
|
|
166
|
+
// scan last" would be an arbitrary, possibly-wrong answer.
|
|
167
|
+
const parsed = new Map<string, unknown>();
|
|
168
|
+
for (const asset of scanned.assets) {
|
|
169
|
+
if (asset.jsonError === null && !duplicateStems.has(asset.name)) {
|
|
170
|
+
parsed.set(asset.name, asset.json);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (const dangling of findDanglingDataRefs(parsed)) {
|
|
174
|
+
const inAsset = scanned.assets.find((a) => a.name === dangling.inAsset);
|
|
175
|
+
const target = scanned.assets.find((a) => a.name === dangling.targetAsset);
|
|
176
|
+
findings.push({
|
|
177
|
+
severity: 'error',
|
|
178
|
+
check: 'data-dangling-ref',
|
|
179
|
+
file: inAsset?.dataPath ?? null,
|
|
180
|
+
message:
|
|
181
|
+
`dangling ref at ${dangling.atPath}: "${dangling.ref}" — ` +
|
|
182
|
+
`"${dangling.missingSegment}" does not exist in ${target?.dataPath ?? `${dangling.targetAsset}.data.json`}. ` +
|
|
183
|
+
'Fix the key or remove the ref (refs are "file#key" strings — docs/DATA-TOOLS-DESIGN.md §2.2).',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// §9.4 fix: a DECLARED `dataRef(target)` field (schema carries
|
|
188
|
+
// `"x-vgai-ref": target`) whose target file doesn't exist, checked by
|
|
189
|
+
// NAME regardless of whether any current value looks like a ref — unlike
|
|
190
|
+
// the dangling-ref check above, this fires even on an asset with no data
|
|
191
|
+
// yet, and survives a target file being renamed/deleted (the exact §9.4
|
|
192
|
+
// hole conventional-string detection couldn't close). A file that exists
|
|
193
|
+
// (even a duplicate-stem one, already flagged above) is never re-flagged
|
|
194
|
+
// here — the check is purely "does the name resolve to SOME file."
|
|
195
|
+
const allAssetNames = new Set(scanned.assets.map((a) => a.name));
|
|
196
|
+
for (const asset of scanned.assets) {
|
|
197
|
+
if (!asset.emittedSchema) continue;
|
|
198
|
+
const declared = collectDeclaredRefFields(asset.emittedSchema);
|
|
199
|
+
for (const missing of findMissingRefTargets(declared, allAssetNames)) {
|
|
200
|
+
findings.push({
|
|
201
|
+
severity: 'error',
|
|
202
|
+
check: 'data-ref-missing-file',
|
|
203
|
+
file: asset.dataPath,
|
|
204
|
+
message:
|
|
205
|
+
`field "${missing.fieldPath}" is declared as dataRef('${missing.targetStem}'), but no ` +
|
|
206
|
+
`"src/data/${missing.targetStem}.data.json" exists. Restore/rename the target file, or ` +
|
|
207
|
+
`update the dataRef('${missing.targetStem}') call in ` +
|
|
208
|
+
`${asset.schemaTsPath ?? 'the schema'} (docs/DATA-TOOLS-DESIGN.md §9.4).`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (scanned.assets.length === 0 && scanned.sourceFileCount >= NON_TRIVIAL_SOURCE_FILE_COUNT) {
|
|
214
|
+
findings.push({
|
|
215
|
+
severity: 'info',
|
|
216
|
+
check: 'data-none',
|
|
217
|
+
file: null,
|
|
218
|
+
message:
|
|
219
|
+
`no data assets found (src/data/*.data.json) in a project with ${scanned.sourceFileCount} ` +
|
|
220
|
+
'source files. If a designer should be able to change it, it belongs in a data asset — ' +
|
|
221
|
+
'a JSON file in src/data/ with a Zod schema (docs/DATA-TOOLS-DESIGN.md §0; the template ' +
|
|
222
|
+
'ships the worked example). Deleting the pattern is a choice — this is a nudge, not a failure.',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return findings;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Checks 1 + 3 for one asset — factored out to keep {@link assembleDataFindings} within the lint complexity budget. */
|
|
230
|
+
function perAssetFindings(asset: ScannedDataAsset): DataDoctorFinding[] {
|
|
231
|
+
const findings: DataDoctorFinding[] = [];
|
|
232
|
+
|
|
233
|
+
if (asset.jsonError !== null) {
|
|
234
|
+
findings.push({
|
|
235
|
+
severity: 'error',
|
|
236
|
+
check: 'data-schema',
|
|
237
|
+
file: asset.dataPath,
|
|
238
|
+
message: `not valid JSON: ${asset.jsonError}. Fix the syntax — every consumer of this file (defineData parse-on-load, the editor Data tab, VS Code) is wedged until it parses.`,
|
|
239
|
+
});
|
|
240
|
+
} else if (asset.validation?.kind === 'invalid') {
|
|
241
|
+
// parseDataJson's own teaching error text, verbatim — it already names
|
|
242
|
+
// the file, every bad path, and the fix (§6.5 "errors teach").
|
|
243
|
+
findings.push({
|
|
244
|
+
severity: 'error',
|
|
245
|
+
check: 'data-schema',
|
|
246
|
+
file: asset.dataPath,
|
|
247
|
+
message: asset.validation.message,
|
|
248
|
+
});
|
|
249
|
+
} else if (asset.validation?.kind === 'unavailable') {
|
|
250
|
+
findings.push({
|
|
251
|
+
severity: 'warn',
|
|
252
|
+
check: 'data-schema',
|
|
253
|
+
file: asset.dataPath,
|
|
254
|
+
message:
|
|
255
|
+
`could not validate against ${asset.schemaTsPath ?? 'its schema'}: ${asset.validation.reason} ` +
|
|
256
|
+
'— not counted as a pass (never fabricated). `npm run emit-schemas` in the project is the ' +
|
|
257
|
+
"same check with the project's own module resolution.",
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (asset.schemaTsPath === null) {
|
|
262
|
+
findings.push({
|
|
263
|
+
severity: 'warn',
|
|
264
|
+
check: 'data-missing-schema',
|
|
265
|
+
file: asset.dataPath,
|
|
266
|
+
message:
|
|
267
|
+
'no governing schema: expected a sibling ' +
|
|
268
|
+
`${asset.dataPath.replace(/\.data\.json$/, '.schema.ts')} (Zod, engineer-owned). ` +
|
|
269
|
+
"Every data asset is a trio — copy the template's src/data/ worked example " +
|
|
270
|
+
'(docs/DATA-TOOLS-DESIGN.md §2.1).',
|
|
271
|
+
});
|
|
272
|
+
} else if (asset.schemaJsonPath === null) {
|
|
273
|
+
findings.push({
|
|
274
|
+
severity: 'warn',
|
|
275
|
+
check: 'data-missing-schema',
|
|
276
|
+
file: asset.dataPath,
|
|
277
|
+
message:
|
|
278
|
+
`the emitted ${asset.dataPath.replace(/\.data\.json$/, '.schema.json')} is missing — run ` +
|
|
279
|
+
'`npm run emit-schemas` and commit it (D4: without it VS Code cannot validate this file ' +
|
|
280
|
+
'and the editor Data tab cannot render typed widgets).',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return findings;
|
|
285
|
+
}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vgai doctor <folder>` — per-world verdict assembly (D-A4,
|
|
3
|
+
* docs/WAVE6-DOCTOR-DESIGN.md Slice A; extended by D-H2,
|
|
4
|
+
* docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md Slice B). PURE
|
|
5
|
+
* data-in/data-out: this module never touches a browser, a filesystem, or a
|
|
6
|
+
* child process — the browser phase (`packages/editor/scripts/run-doctor.ts`,
|
|
7
|
+
* NOT in the unit gate per the Slice-A NOTs) does all the scraping (spawn the
|
|
8
|
+
* real dev server, drive the real editor page, read the `__vgaiIngest*`/
|
|
9
|
+
* `__vgaiModule`/`__vgaiMountFailureReports()`/`__vgaiSiblingMounts()` window
|
|
10
|
+
* signals D-A3/D-H1 add/expose) and hands its findings to
|
|
11
|
+
* {@link assembleDoctorReport} as plain data, so the verdict RULES
|
|
12
|
+
* themselves — the actual "what counts as proof" logic F19 exists to get
|
|
13
|
+
* right — are unit-testable without ever booting Chromium.
|
|
14
|
+
*
|
|
15
|
+
* Doctrine (binding, docs/WAVE6-DOCTOR-DESIGN.md doctrine header): anti-shim
|
|
16
|
+
* — never fabricate a PASS. Every declared world gets EXACTLY one verdict:
|
|
17
|
+
*
|
|
18
|
+
* - **MOUNTED** — real evidence cleared the bar for this world's route
|
|
19
|
+
* (see {@link DoctorIngestEvidence}/{@link DoctorModuleEvidence}/
|
|
20
|
+
* {@link DoctorReactIngestEvidence} — the last added wave 10 Slice B,
|
|
21
|
+
* docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md D-L3), OR a composite
|
|
22
|
+
* sibling was present in the live sibling-mount list (see
|
|
23
|
+
* {@link DoctorSiblingEvidence} — a deliberately WEAKER, presence-only
|
|
24
|
+
* bar, checked last, never conflated with the ingest-capture bar above).
|
|
25
|
+
* - **FAILED** — a `__vgaiMountFailureReports()` entry named this world;
|
|
26
|
+
* its message is carried verbatim. Checked FIRST, ahead of any evidence
|
|
27
|
+
* map — a throwing mount attempt can leave stale/partial evidence behind
|
|
28
|
+
* (defensive; today's runner never populates both for the same world).
|
|
29
|
+
* - **UNVERIFIABLE** — neither of the above: no proof, but no named
|
|
30
|
+
* failure either. This is the "never silently omitted, never upgraded
|
|
31
|
+
* to MOUNTED" floor AC-A6 requires, not an error state of this module.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type { DataDoctorFinding } from './data-checks';
|
|
35
|
+
|
|
36
|
+
/** One declared world from the manifest — enough identity to label a verdict row. */
|
|
37
|
+
export interface DoctorDeclaredWorld {
|
|
38
|
+
readonly worldId: string;
|
|
39
|
+
/** `ResolvedWorldEntry.kind` (e.g. `'threejs'`/`'pixijs'`/`'react'`). */
|
|
40
|
+
readonly kind: string;
|
|
41
|
+
/** `ResolvedAdapter['identity']` (e.g. `'ingest-three'`/`'module'`/`'default-three'`). */
|
|
42
|
+
readonly identity: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Scraped evidence for an ingest-capture world (`ingest-three`/`ingest-pixi`
|
|
47
|
+
* — the two identities whose healthy `__vgaiIngest`/`__vgaiIngest2D` payload
|
|
48
|
+
* carries `reflected`/`drawCount`). `null` fields mean "the runner found no
|
|
49
|
+
* such signal at all" (e.g. the world never mounted this session) — distinct
|
|
50
|
+
* from a present-but-failing value (e.g. `reflectedCount: 0`), which still
|
|
51
|
+
* means SOME payload was observed, just not enough to prove MOUNTED.
|
|
52
|
+
*/
|
|
53
|
+
export interface DoctorIngestEvidence {
|
|
54
|
+
readonly reflectedCount: number | null;
|
|
55
|
+
/** Whether `drawCount()` was sampled twice across an interval and strictly climbed. `null` = no `drawCount` function was found to sample. */
|
|
56
|
+
readonly drawCountClimbed: boolean | null;
|
|
57
|
+
/** `typeof setPaused === 'function'` on the payload. */
|
|
58
|
+
readonly playControlLive: boolean;
|
|
59
|
+
/** True only for a genuinely tier-disabled floor (embed-only: no loop exists to gate) — never a fake no-op standing in for a real capability. */
|
|
60
|
+
readonly playControlTierDisabled: boolean;
|
|
61
|
+
readonly achievedTier: string | null;
|
|
62
|
+
readonly declaredTier: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Scraped evidence for a `{ module }`-route world (`__vgaiModule`) — D-A4: presence + achieved/declared tier is the whole bar (no reflected/drawCount concept for a custom adapter). */
|
|
66
|
+
export interface DoctorModuleEvidence {
|
|
67
|
+
readonly achievedTier: string;
|
|
68
|
+
readonly declaredTier: string | null;
|
|
69
|
+
readonly tierShortfall: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One `__vgaiMountFailureReports()` entry, trimmed to what the verdict needs. */
|
|
73
|
+
export interface DoctorMountFailure {
|
|
74
|
+
readonly worldId: string;
|
|
75
|
+
readonly message: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Scraped evidence for a react-ingest world's LIVE DOM-authoring surface
|
|
80
|
+
* (`__vgaiIngestReact.domEvidence()` + `setPausedPresent`, wave 10 Slice B,
|
|
81
|
+
* docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md D-L1/D-L2). The MOUNTED
|
|
82
|
+
* bar for this class is {@link clearsReactIngestMountedBar} — ALL four legs,
|
|
83
|
+
* never partial credit (anti-shim, AC-L2/AC-L3).
|
|
84
|
+
*/
|
|
85
|
+
export interface DoctorReactIngestEvidence {
|
|
86
|
+
readonly elementCount: number;
|
|
87
|
+
readonly hierarchyNodeCount: number;
|
|
88
|
+
readonly reactRootPresent: boolean;
|
|
89
|
+
readonly setPausedPresent: boolean;
|
|
90
|
+
readonly achievedTier: string | null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Scraped evidence for a COMPOSITE SIBLING world (`window.__vgaiSiblingMounts()`,
|
|
95
|
+
* D-H1, docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md Slice B) — deliberately
|
|
96
|
+
* a DISTINCT class from {@link DoctorIngestEvidence}/{@link DoctorModuleEvidence}
|
|
97
|
+
* (D-H2): the bar for this evidence is PRESENCE in the live sibling-mount list,
|
|
98
|
+
* not a capture/draw proof. Never conflate this with `clearsIngestMountedBar`'s
|
|
99
|
+
* ingest-capture bar — a sibling's evidence class name says, on its face, that
|
|
100
|
+
* this is the weaker of the two.
|
|
101
|
+
*/
|
|
102
|
+
export interface DoctorSiblingEvidence {
|
|
103
|
+
/** `typeof sibling.setPaused === 'function'` — absent for a bare `default-react` sibling (no loop to gate). */
|
|
104
|
+
readonly setPausedPresent: boolean;
|
|
105
|
+
/** `!!sibling.authoring` — present whenever the sibling mounted with a store (every production caller). */
|
|
106
|
+
readonly authoringPresent: boolean;
|
|
107
|
+
/** `sibling.authoring?.capabilities.persist === true` — false/absent authoring never reads as persistable. */
|
|
108
|
+
readonly authoringPersist: boolean;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Every raw datum the browser-phase runner scraped off the live page —
|
|
113
|
+
* data-in for {@link assembleDoctorReport}. Maps/sets are keyed by worldId so
|
|
114
|
+
* a world with no entry anywhere here means "no signal was ever observed for
|
|
115
|
+
* it" (the UNVERIFIABLE floor, never fabricated as MOUNTED).
|
|
116
|
+
*/
|
|
117
|
+
export interface DoctorRawSignals {
|
|
118
|
+
readonly ingestEvidence: ReadonlyMap<string, DoctorIngestEvidence>;
|
|
119
|
+
readonly moduleEvidence: ReadonlyMap<string, DoctorModuleEvidence>;
|
|
120
|
+
/** worldIds observed via `__vgaiIngestReact` — presence-only (D-N8: no authoring/capture surface in v1), deliberately NOT treated as proof of a working mount (see UNVERIFIABLE branch below). */
|
|
121
|
+
readonly reactIngestPresence: ReadonlySet<string>;
|
|
122
|
+
/**
|
|
123
|
+
* Per-worldId composite-sibling evidence (D-H2) — a presence-based bar,
|
|
124
|
+
* checked AFTER ingest/module evidence and react presence, BEFORE the
|
|
125
|
+
* terminal UNVERIFIABLE branch (see {@link assembleDoctorReport}).
|
|
126
|
+
* OPTIONAL (unlike the other maps above) so every pre-D-H2 `DoctorRawSignals`
|
|
127
|
+
* literal (existing tests, any other caller) keeps compiling/running
|
|
128
|
+
* byte-stable — an absent map reads exactly like an empty one.
|
|
129
|
+
*/
|
|
130
|
+
readonly siblingEvidence?: ReadonlyMap<string, DoctorSiblingEvidence>;
|
|
131
|
+
/**
|
|
132
|
+
* Per-worldId react-ingest DOM evidence (D-L2, wave 10 Slice B,
|
|
133
|
+
* docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md) — checked in
|
|
134
|
+
* {@link assembleDoctorReport} after ingest/module evidence, before the
|
|
135
|
+
* legacy `reactIngestPresence`-only branch and the sibling branch (which
|
|
136
|
+
* stays LAST). OPTIONAL (unlike `reactIngestPresence`) so every
|
|
137
|
+
* pre-Slice-B `DoctorRawSignals` literal keeps compiling/running
|
|
138
|
+
* byte-stable — an absent map reads exactly like an empty one (the same
|
|
139
|
+
* wave-9B lesson `siblingEvidence` above already applies).
|
|
140
|
+
*/
|
|
141
|
+
readonly reactIngestEvidence?: ReadonlyMap<string, DoctorReactIngestEvidence>;
|
|
142
|
+
readonly mountFailures: readonly DoctorMountFailure[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
interface DoctorVerdictBase extends DoctorDeclaredWorld {}
|
|
146
|
+
|
|
147
|
+
export interface DoctorMountedVerdict extends DoctorVerdictBase {
|
|
148
|
+
readonly verdict: 'MOUNTED';
|
|
149
|
+
readonly evidence:
|
|
150
|
+
| DoctorIngestEvidence
|
|
151
|
+
| DoctorModuleEvidence
|
|
152
|
+
| DoctorSiblingEvidence
|
|
153
|
+
| DoctorReactIngestEvidence;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface DoctorFailedVerdict extends DoctorVerdictBase {
|
|
157
|
+
readonly verdict: 'FAILED';
|
|
158
|
+
readonly message: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface DoctorUnverifiableVerdict extends DoctorVerdictBase {
|
|
162
|
+
readonly verdict: 'UNVERIFIABLE';
|
|
163
|
+
readonly reason: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export type DoctorVerdict = DoctorMountedVerdict | DoctorFailedVerdict | DoctorUnverifiableVerdict;
|
|
167
|
+
|
|
168
|
+
/** True iff an ingest-capture evidence bundle proves a real, live, editable mount (D-A4's MOUNTED bar for ingest worlds). */
|
|
169
|
+
function clearsIngestMountedBar(ev: DoctorIngestEvidence): boolean {
|
|
170
|
+
const reflectedOk = ev.reflectedCount !== null && ev.reflectedCount > 0;
|
|
171
|
+
const drawOk = ev.drawCountClimbed === true;
|
|
172
|
+
const playOk = ev.playControlLive || ev.playControlTierDisabled;
|
|
173
|
+
return reflectedOk && drawOk && playOk;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Named, honest reason an ingest world's evidence fell short of the MOUNTED bar — never silently downgraded without saying why. */
|
|
177
|
+
function ingestShortfallReason(worldId: string, ev: DoctorIngestEvidence): string {
|
|
178
|
+
return (
|
|
179
|
+
`world "${worldId}" produced an ingest-capture signal but did not clear the MOUNTED bar ` +
|
|
180
|
+
`(reflectedCount=${ev.reflectedCount ?? 'null'}, drawCountClimbed=${ev.drawCountClimbed ?? 'null'}, ` +
|
|
181
|
+
`playControlLive=${ev.playControlLive}, playControlTierDisabled=${ev.playControlTierDisabled}) — ` +
|
|
182
|
+
'never upgraded to MOUNTED without all three (docs/WAVE6-DOCTOR-DESIGN.md D-A4).'
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** True iff react-ingest DOM evidence proves a real, live, editable mount (D-L3's MOUNTED bar for react-ingest worlds) — ALL four legs, never partial credit. */
|
|
187
|
+
function clearsReactIngestMountedBar(ev: DoctorReactIngestEvidence): boolean {
|
|
188
|
+
return (
|
|
189
|
+
ev.elementCount > 0 && ev.hierarchyNodeCount > 0 && ev.reactRootPresent && ev.setPausedPresent
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Named, honest reason a react-ingest world's DOM evidence fell short of the MOUNTED bar — names exactly which leg(s), never silently downgraded without saying why. */
|
|
194
|
+
function reactIngestShortfallReason(worldId: string, ev: DoctorReactIngestEvidence): string {
|
|
195
|
+
const failedLegs: string[] = [];
|
|
196
|
+
if (!(ev.elementCount > 0)) failedLegs.push(`elementCount=${ev.elementCount}`);
|
|
197
|
+
if (!(ev.hierarchyNodeCount > 0)) failedLegs.push(`hierarchyNodeCount=${ev.hierarchyNodeCount}`);
|
|
198
|
+
if (!ev.reactRootPresent) failedLegs.push('reactRootPresent=false');
|
|
199
|
+
if (!ev.setPausedPresent) failedLegs.push('setPausedPresent=false');
|
|
200
|
+
return (
|
|
201
|
+
`world "${worldId}" produced react-ingest DOM evidence but did not clear the MOUNTED bar ` +
|
|
202
|
+
`(${failedLegs.join(', ')}) — never upgraded to MOUNTED without all four legs ` +
|
|
203
|
+
'(docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md D-L3).'
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Verdict for a world with ingest-capture evidence — factored out of {@link assembleDoctorReport} to keep its own cognitive complexity within the repo's lint budget. */
|
|
208
|
+
function verdictForIngestEvidence(
|
|
209
|
+
world: DoctorDeclaredWorld,
|
|
210
|
+
ev: DoctorIngestEvidence,
|
|
211
|
+
): DoctorVerdict {
|
|
212
|
+
const { worldId, kind, identity } = world;
|
|
213
|
+
return clearsIngestMountedBar(ev)
|
|
214
|
+
? { worldId, kind, identity, verdict: 'MOUNTED', evidence: ev }
|
|
215
|
+
: {
|
|
216
|
+
worldId,
|
|
217
|
+
kind,
|
|
218
|
+
identity,
|
|
219
|
+
verdict: 'UNVERIFIABLE',
|
|
220
|
+
reason: ingestShortfallReason(worldId, ev),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Verdict for a world with react-ingest DOM evidence (D-L3) — same factoring reason as {@link verdictForIngestEvidence} above. */
|
|
225
|
+
function verdictForReactIngestEvidence(
|
|
226
|
+
world: DoctorDeclaredWorld,
|
|
227
|
+
ev: DoctorReactIngestEvidence,
|
|
228
|
+
): DoctorVerdict {
|
|
229
|
+
const { worldId, kind, identity } = world;
|
|
230
|
+
return clearsReactIngestMountedBar(ev)
|
|
231
|
+
? { worldId, kind, identity, verdict: 'MOUNTED', evidence: ev }
|
|
232
|
+
: {
|
|
233
|
+
worldId,
|
|
234
|
+
kind,
|
|
235
|
+
identity,
|
|
236
|
+
verdict: 'UNVERIFIABLE',
|
|
237
|
+
reason: reactIngestShortfallReason(worldId, ev),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Assemble exactly one verdict per declared world (AC-A4). Iterates
|
|
243
|
+
* `declaredWorlds` (never the signal maps) so a world with zero matching
|
|
244
|
+
* signals still gets its own UNVERIFIABLE row rather than being dropped.
|
|
245
|
+
*/
|
|
246
|
+
export function assembleDoctorReport(
|
|
247
|
+
declaredWorlds: readonly DoctorDeclaredWorld[],
|
|
248
|
+
raw: DoctorRawSignals,
|
|
249
|
+
): readonly DoctorVerdict[] {
|
|
250
|
+
const failureByWorld = new Map(raw.mountFailures.map((f) => [f.worldId, f.message]));
|
|
251
|
+
|
|
252
|
+
return declaredWorlds.map((world): DoctorVerdict => {
|
|
253
|
+
const { worldId, kind, identity } = world;
|
|
254
|
+
|
|
255
|
+
// FAILED is checked first — a named failure is the strongest, most
|
|
256
|
+
// actionable signal doctor can report, and takes precedence over any
|
|
257
|
+
// (possibly stale/partial) evidence left behind by the same attempt.
|
|
258
|
+
const failureMessage = failureByWorld.get(worldId);
|
|
259
|
+
if (failureMessage !== undefined) {
|
|
260
|
+
return { worldId, kind, identity, verdict: 'FAILED', message: failureMessage };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const ingestEv = raw.ingestEvidence.get(worldId);
|
|
264
|
+
if (ingestEv) return verdictForIngestEvidence(world, ingestEv);
|
|
265
|
+
|
|
266
|
+
const moduleEv = raw.moduleEvidence.get(worldId);
|
|
267
|
+
if (moduleEv) {
|
|
268
|
+
return { worldId, kind, identity, verdict: 'MOUNTED', evidence: moduleEv };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// D-L3 (wave 10 Slice B, docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md):
|
|
272
|
+
// checked in the SAME position the presence-only D-N8 branch used to
|
|
273
|
+
// occupy — after ingest/module evidence, before the sibling branch
|
|
274
|
+
// (which stays LAST) — so a world whose ingest evidence already fell
|
|
275
|
+
// short above is NEVER reconsidered here, and below-bar react evidence
|
|
276
|
+
// is NEVER reconsidered by the sibling branch below (AC-L5, anti-shim
|
|
277
|
+
// ordering pin: mirrors the ingest-evidence early-return above).
|
|
278
|
+
const reactEv = raw.reactIngestEvidence?.get(worldId);
|
|
279
|
+
if (reactEv) return verdictForReactIngestEvidence(world, reactEv);
|
|
280
|
+
|
|
281
|
+
if (raw.reactIngestPresence.has(worldId)) {
|
|
282
|
+
return {
|
|
283
|
+
worldId,
|
|
284
|
+
kind,
|
|
285
|
+
identity,
|
|
286
|
+
verdict: 'UNVERIFIABLE',
|
|
287
|
+
reason:
|
|
288
|
+
`world "${worldId}" mounted an ingest-react session (__vgaiIngestReact present), but no ` +
|
|
289
|
+
'`domEvidence()` accessor was found on its payload (an older build predating wave 10 ' +
|
|
290
|
+
'Slice B, docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md D-L1) — presence alone is not ' +
|
|
291
|
+
'proof of a working mount, so this is reported honestly rather than upgraded to MOUNTED.',
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// D-H2 (docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md Slice B): checked
|
|
296
|
+
// LAST among the "some evidence exists" branches, after ingest/module
|
|
297
|
+
// evidence and react presence — so a world whose ingest evidence exists
|
|
298
|
+
// but fell short of `clearsIngestMountedBar` above is NEVER reconsidered
|
|
299
|
+
// here (AC-H4, anti-shim: sibling evidence never upgrades a below-the-bar
|
|
300
|
+
// ingest world). The bar itself is presence-in-the-live-list — parallel
|
|
301
|
+
// to the module-route bar above — and the evidence class name
|
|
302
|
+
// ({@link DoctorSiblingEvidence}) makes that weaker basis explicit; never
|
|
303
|
+
// conflate it with `clearsIngestMountedBar`'s capture/draw proof.
|
|
304
|
+
const siblingEv = raw.siblingEvidence?.get(worldId);
|
|
305
|
+
if (siblingEv) {
|
|
306
|
+
return { worldId, kind, identity, verdict: 'MOUNTED', evidence: siblingEv };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
worldId,
|
|
311
|
+
kind,
|
|
312
|
+
identity,
|
|
313
|
+
verdict: 'UNVERIFIABLE',
|
|
314
|
+
reason:
|
|
315
|
+
`no headless health signal was observed for world "${worldId}" (kind "${kind}", identity ` +
|
|
316
|
+
`"${identity}"), and no mount-failure entry named it either — either this identity has no ` +
|
|
317
|
+
'headless signal wired in doctor this slice, or the mount never ran at all.',
|
|
318
|
+
};
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// The full `--json` envelope (D-A5) — also pure; the CLI/runner only supply
|
|
324
|
+
// the already-computed static summary + verdict list.
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
/** Static (Phase 1) result summary, as far as the JSON report cares (AC-A1). */
|
|
328
|
+
export interface DoctorStaticSummary {
|
|
329
|
+
readonly ok: boolean;
|
|
330
|
+
/** Human-readable issues (manifest errors / missing declared files) — empty when `ok`. */
|
|
331
|
+
readonly issues: readonly string[];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Bump if this JSON shape ever changes incompatibly — consumers should gate
|
|
336
|
+
* on it. Bumped 1 -> 2 (D-H4, docs/WAVE9-SIBLING-PERSISTENCE-DOCTOR-DESIGN.md
|
|
337
|
+
* Slice B): a `MOUNTED` verdict's `evidence` union gained the
|
|
338
|
+
* {@link DoctorSiblingEvidence} variant. Bumped 2 -> 3 (D-L4, wave 10 Slice B,
|
|
339
|
+
* docs/WAVE10-REACT-INGEST-DOM-AUTHORING-DESIGN.md): the `evidence` union
|
|
340
|
+
* gained the {@link DoctorReactIngestEvidence} variant (a react-ingest world
|
|
341
|
+
* can now MOUNT on real DOM evidence, not just be UNVERIFIABLE). Bumped
|
|
342
|
+
* 3 -> 4 (docs/DATA-TOOLS-DESIGN.md §6.6, W5 enforcement): the envelope
|
|
343
|
+
* gained the optional top-level `data` section (data-asset findings), and an
|
|
344
|
+
* ERROR-severity data finding now flips the exit code (see
|
|
345
|
+
* {@link doctorExitCode}).
|
|
346
|
+
*/
|
|
347
|
+
export const DOCTOR_VERSION = 4 as const;
|
|
348
|
+
|
|
349
|
+
export interface DoctorJsonReport {
|
|
350
|
+
readonly doctorVersion: typeof DOCTOR_VERSION;
|
|
351
|
+
readonly static: DoctorStaticSummary;
|
|
352
|
+
readonly worlds: readonly DoctorVerdict[];
|
|
353
|
+
/** Data-asset check findings (docs/DATA-TOOLS-DESIGN.md §6.6) — present iff the caller ran the data scan (run-doctor.ts always does; older/other callers may not). */
|
|
354
|
+
readonly data?: { readonly findings: readonly DataDoctorFinding[] };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Build the stable `--json` envelope (D-A5: "stable shape, top-level `doctorVersion`, static-phase result included"; the version literal itself is {@link DOCTOR_VERSION}, currently `4`). The `data` section is included only when findings were actually scanned — never fabricated as an empty pass. */
|
|
358
|
+
export function buildDoctorJsonReport(
|
|
359
|
+
staticSummary: DoctorStaticSummary,
|
|
360
|
+
worldVerdicts: readonly DoctorVerdict[],
|
|
361
|
+
dataFindings?: readonly DataDoctorFinding[],
|
|
362
|
+
): DoctorJsonReport {
|
|
363
|
+
const base: DoctorJsonReport = {
|
|
364
|
+
doctorVersion: DOCTOR_VERSION,
|
|
365
|
+
static: staticSummary,
|
|
366
|
+
worlds: worldVerdicts,
|
|
367
|
+
};
|
|
368
|
+
return dataFindings ? { ...base, data: { findings: dataFindings } } : base;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* D-A5: "Exit 0 iff static passed AND no world is FAILED. Exit 1 otherwise.
|
|
373
|
+
* UNVERIFIABLE does not flip the exit code." Extended by W5 enforcement
|
|
374
|
+
* (docs/DATA-TOOLS-DESIGN.md §6.6): an ERROR-severity data-asset finding
|
|
375
|
+
* (schema failure, dangling ref) also exits 1 — warn/info findings never do,
|
|
376
|
+
* mirroring how UNVERIFIABLE worlds never do.
|
|
377
|
+
*/
|
|
378
|
+
export function doctorExitCode(
|
|
379
|
+
staticSummary: DoctorStaticSummary,
|
|
380
|
+
worldVerdicts: readonly DoctorVerdict[],
|
|
381
|
+
dataFindings?: readonly DataDoctorFinding[],
|
|
382
|
+
): 0 | 1 {
|
|
383
|
+
if (!staticSummary.ok) return 1;
|
|
384
|
+
if (dataFindings?.some((f) => f.severity === 'error')) return 1;
|
|
385
|
+
return worldVerdicts.some((v) => v.verdict === 'FAILED') ? 1 : 0;
|
|
386
|
+
}
|