diff --git a/ASSET-LICENSES.md b/ASSET-LICENSES.md index effffe0..aa95585 100644 --- a/ASSET-LICENSES.md +++ b/ASSET-LICENSES.md @@ -9,6 +9,7 @@ entry and provenance record here for the asset or release you want to reuse. | Status | What it means | | --- | --- | | **CC-BY-4.0** | An open-content grant for the expressly named material. Sharing and adaptation, including commercial use, require attribution, a license link, and an indication of changes. | +| **Public draft review authorized; release pending** | Warpkeep may expose the named candidate's lightweight metadata and previews in a draft PR. Binary release publication and any broader reuse grant remain pending. | | **Public archive authorized; no separate open-license grant** | Warpkeep may preserve and publish the named deposit. The entry does not give the public a general right to redistribute, adapt, sublicense, or reuse it outside the recorded terms. | | **Original or unresolved terms** | Third-party, externally governed, or uncertain-rights material keeps its own terms. Repository presence does not relicense it. | @@ -244,3 +245,20 @@ archive metadata. It does not grant rights in third-party settings or names, generation services, Warpkeep trademarks or canonical identity, or unrelated Warpkeep material by implication. The release describes The Core only as a Warpkeep faction and does not include outside reference imagery or branding. + +## Warpkeep Core Watcher — Level 1 release candidate + +- **Set:** one original Core Watcher; four texture-free static runtime GLBs; one editable Blender source; exact previews; manifests; QA; and checksums +- **Candidate tag:** `core-watcher-level1-2026-08-03` +- **Snapshot date:** 2026-08-03 +- **Review authority:** Ael explicitly authorized preparing this new, isolated draft PR in Warpkeep-Assets. +- **Creation disclosure:** the geometry, materials, LODs, renders, and package metadata were substantially authored and prepared with Codex under Ael's direction and review +- **License status:** public draft review authorized; release pending; no separate open-license grant asserted + +This authorization covers the lightweight candidate metadata, exact rendered +previews, provenance, and verification software in this draft PR. It does not +authorize uploading the prepared binary ZIP, creating its intended tag or +GitHub Release, integrating the asset into Warpkeep, or merging this PR. It +does not license third-party tools or services, third-party reference material, +Warpkeep trademarks or canonical identity, the separate Core faction crest release, +or unrelated Warpkeep material by implication. diff --git a/README.md b/README.md index a5d4d32..74b529f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,67 @@ Integration notes below describe what each asset release itself authorized or asserted when it was published. See the [Warpkeep repository](https://github.com/ael-dev3/Warpkeep) for the current live runtime. +[![Warpkeep Core Watcher Level 1 release candidate](previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg)](previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg) + +## Core Watcher — Level 1 release candidate + +The Core Watcher is a new original encounter silhouette for the first slice of +The Core Stirs: a tall bifurcated obsidian monolith, a suspended cold-violet +core, and asymmetric floating shards. The candidate package contains one +editable Blender source and four texture-free, self-contained static GLBs for +close inspection through far-map presentation. + +| LOD | Intended view | Triangles | Size | +| --- | --- | ---: | ---: | +| LOD0 High | Selected inspection | 2,354 | 126.5 KiB | +| LOD1 Balanced | Nearby Realm view | 1,000 | 66.4 KiB | +| LOD2 Compact | Medium distance | 282 | 27.0 KiB | +| LOD3 Map | Far-map signal | 158 | 18.3 KiB | + +All four runtime files pass the official glTF validator with zero issues and +clean-import into Blender without cameras, lights, rigs, or actions. The +package's own report records **58/58 checks passed**, including strict LOD and +byte reduction, embedded-only data, bounded geometry, Level 1 identity, and a +visual-only authority boundary. + +This is deliberately a **draft review candidate**, not a published release or +live-game integration. No tag, GitHub Release attachment, catalogue entry, +combat behavior, placement, deployment, or activation is created by this PR. +The prepared ZIP remains local until an owner explicitly approves publication. + +[candidate manifest](releases/core-watcher-level1-2026-08-03/manifest.json) · [provenance](provenance/core-watcher-level1-2026-08-03.md) · [sanitization audit](reports/core-watcher-level1-2026-08-03/public-sanitization.json) · [external validation](reports/core-watcher-level1-2026-08-03/external-validation.json) + +### Integration-readiness contract + +Reviewers can inspect the package's [byte-exact runtime manifest](contracts/core-watcher-level1-2026-08-03.runtime-manifest.json) +without possessing the unpublished ZIP. A separate, digest-bound [advisory +integration profile](contracts/core-watcher-level1-2026-08-03.integration-profile.json) +turns the current Realm's renderer expectations into a reviewable handoff: +content-addressed and fail-closed loading, quality/camera LOD allocation, +component-wise instancing, bounded presentation motion, mobile gesture +coexistence, 44–48px controls, reduced-quality and reduced-motion behavior, +fallback discoverability, accessibility, telemetry privacy, and exhaustive +server-authority exclusions. + +The profile includes a **non-authoritative 72-instance capacity test** for the +Map LOD: 11,376 visible triangles and 12 component-wise instanced draw groups, +instead of 864 naive clone draws. It does not declare that 72 sites exist or +supply any site ID, coordinate, seed, hidden state, catalogue, combat rule, +database row, client loader, release permission, or activation permission. +Those gameplay surfaces remain explicitly assigned to later owner-gated PR +slices in the main Warpkeep repository. + +### Candidate gallery + +| Four-LOD comparison | Far-map preview | +| --- | --- | +| [![Core Watcher four-LOD comparison](previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg)](previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg) | [![Core Watcher far-map preview](previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png)](previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png) | + +*The tracked images are re-encoded without descriptive or private metadata from +the exact Blender-rendered frames in the candidate archive: a 1920×1080 +presentation, a 2400×1200 four-LOD sheet, and a 512×512 mobile map preview. +The JPEGs retain only the standard JFIF container header recorded by the audit.* + [![Warpkeep — The Core faction crest](previews/the-core-faction-crest-2026-08-03/00-the-core-faction-crest-showcase.jpg)](https://github.com/ael-dev3/Warpkeep-Assets/releases/tag/the-core-faction-crest-2026-08-03) ## The Core faction crest @@ -129,6 +190,7 @@ families, and the exact-texture Hegemony banner collection. - [`releases/`](releases/) — trusted attachment manifests and checksum sidecars - [`manifests/`](manifests/) — source inventories and preparation records +- [`contracts/`](contracts/) — reviewable asset/runtime handoff contracts; never gameplay authority - [`provenance/`](provenance/) — authorization, history, and license boundaries - [`previews/`](previews/) — lightweight Git-tracked visual catalogues - [`scripts/`](scripts/) and [`tests/`](tests/) — fail-closed release verification @@ -201,6 +263,26 @@ Use a manifest from a trusted repository commit. If its SHA-256 was obtained through a separate trusted channel, pin it with `--manifest-sha256`; checksums prove integrity against that manifest, not the identity of whoever supplied it. +Reviewers holding the local Core Watcher candidate can additionally run its +deep structural verifier: + +```sh +python3 scripts/verify_core_watcher_level1.py \ + /path/to/warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip +python3 scripts/verify_release.py \ + --manifest releases/core-watcher-level1-2026-08-03/manifest.json \ + --asset-dir /path/to/candidate-directory +python3 scripts/verify_core_watcher_integration_profile.py +``` + +The focused verifier checks the internal model and package contract. The +tracked release manifest and checksum sidecar are the separate trust anchor +for the exact candidate ZIP bytes; use both checks for authenticity and +structure. The integration-profile verifier requires no unpublished binaries: +it proves that the tracked review copy, candidate manifest, preview evidence, +LOD facts, digest, fail-closed renderer guidance, and no-authority gates remain +internally consistent. + The verifier requires the release checksum sidecar and rejects unsupported media types, wrong bytes, non-regular files, malformed PNG metadata, unexpected ZIP entries, unsafe paths, symlinks, duplicates, control diff --git a/contracts/core-watcher-level1-2026-08-03.integration-profile.json b/contracts/core-watcher-level1-2026-08-03.integration-profile.json new file mode 100644 index 0000000..6043701 --- /dev/null +++ b/contracts/core-watcher-level1-2026-08-03.integration-profile.json @@ -0,0 +1,579 @@ +{ + "assetBinding": { + "archive": { + "bytes": 1405757, + "entries": 15, + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33" + }, + "gallery": { + "bytes": 1255, + "sha256": "90ec48066c69c6a4223dbc3911784c08c6dc86cad686d3581f4cfcfd15cab6bc", + "trackedPath": "previews/core-watcher-level1-2026-08-03/gallery.json" + }, + "packageRoot": "Warpkeep_CoreWatcher_Level1_GameReady", + "releaseManifest": { + "bytes": 7520, + "sha256": "60eaa17e477d37f42d25b446ab9a907c6d57b919007b3632c3e5f327283113f0", + "trackedPath": "releases/core-watcher-level1-2026-08-03/manifest.json" + }, + "runtimeManifest": { + "bytes": 7462, + "packagePath": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/runtime-manifest.json", + "sha256": "0339dc9abe5c6a9340ef9be1d0ad908a5130eb9339a7ace4fafdb25a7548d1fd", + "trackedPath": "contracts/core-watcher-level1-2026-08-03.runtime-manifest.json" + }, + "sourceManifest": { + "bytes": 2416, + "sha256": "2c52e4744914b27fad6ba6b1e28ae1a02363612080079ac62933fd16ce580b04", + "trackedPath": "manifests/core-watcher-level1-2026-08-03.source.json" + }, + "sourceSemanticFingerprintSha256": "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c" + }, + "authorityBoundary": { + "actions": false, + "activation": false, + "ai": false, + "catalog": false, + "collision": false, + "combat": false, + "combatState": false, + "cooldowns": false, + "damage": false, + "encounterState": false, + "fogOfWar": false, + "health": false, + "loot": false, + "networking": false, + "ownership": false, + "pathing": false, + "persistence": false, + "picking": false, + "placement": false, + "placementEligibility": false, + "populationCount": false, + "respawn": false, + "rewards": false, + "routing": false, + "selection": false, + "siteIdentity": false, + "spacetimeDb": false, + "visibility": false, + "visualOnly": true, + "worldCoordinates": false + }, + "contractDigest": { + "algorithm": "sha256", + "canonicalization": "exact tracked UTF-8 bytes with the 64 lowercase hexadecimal characters at $.contractDigest.sha256 replaced by 64 ASCII zeroes; no other transformation", + "sha256": "0a34614dfb42f754fd2524b23ef213c2db502768ad9230bd6a27a9198a8251c0" + }, + "fallbackAndAccessibility": { + "accessibleName": "Level 1 Core Watcher", + "cameraNeutralInspect": true, + "completeSemanticExploreListAlwaysAvailable": true, + "engineGenerated": true, + "fallbackShape": "bifurcated-spire-marker", + "historyBackClosesInspectionBeforeLeavingRealm": true, + "locateIsTheOnlyCameraMovingAction": true, + "modelFailurePreservesPublicRecord": true, + "opaqueIdentifiersExcludedFromDomAndAltText": true, + "presentationArtDecorative": true, + "preserveSelection": true, + "required": true, + "retryPolicy": "client-owned-bounded-no-retry-storm", + "semanticDataSource": "engine-and-server-public-projection", + "testMatrix": { + "keyboardAndScreenReader": true, + "pageZoomPercent": 200, + "prefersReducedMotion": true, + "safariIphone": { + "pageScroll": true, + "pinchZoom": true, + "safeAreaInsets": true, + "visualViewportResize": true + }, + "viewportCssPixels": [ + 390, + 844 + ] + } + }, + "futureGameplaySlices": [ + { + "implementedHere": false, + "owner": "Warpkeep public catalogue and server placement authority", + "slice": "B", + "surface": "public Core site identities, eligible cells, selected-world topology, hydrology, and visibility projection" + }, + { + "implementedHere": false, + "owner": "Warpkeep Realm client", + "slice": "C", + "surface": "content-addressed loading, markers, LOD allocation, instancing, picking, mobile gestures, fallback, and accessibility" + }, + { + "implementedHere": false, + "owner": "SpacetimeDB and authoritative Realm UI", + "slice": "D", + "surface": "encounter state, combat, health, damage, actions, rewards, cooldowns, and persistence" + }, + { + "implementedHere": false, + "owner": "Warpkeep QA and operators", + "slice": "E", + "surface": "desktop and mobile budgets, migrations, observability, recovery, and rollout evidence" + }, + { + "implementedHere": false, + "owner": "Warpkeep owner", + "slice": "F", + "surface": "fail-closed activation and staged rollout" + } + ], + "geometry": { + "frontAxis": "+Z", + "nativeScaleMetersPerUnit": 1.0, + "pivot": "footprint-center-ground", + "renderGeometryIsCollision": false, + "renderGeometryIsPicking": false, + "selectionHint": { + "centerYMeters": 1.275, + "engineOwned": true, + "heightMeters": 2.55, + "presentationFootprintRadiusMeters": 0.9, + "packageSuggestedRadiusMeters": 0.72, + "shape": "cylinder" + }, + "units": "meters", + "upAxis": "+Y" + }, + "identity": { + "accessibleName": "Level 1 Core Watcher", + "assetId": "warpkeep.encounters.core.watcher.level1", + "combatEnabled": false, + "displayName": "Core Watcher", + "encounterLevel": 1, + "enemyKind": "core-watcher", + "faction": "The Core", + "revision": "genesis-001-core-watcher-level1-2026-08-03", + "statePresentation": "dormant-presence" + }, + "instancing": { + "capacityEvidence": { + "declaresPopulation": false, + "mapProfileInstancedDrawGroups": 12, + "mapProfileNaiveCloneDrawCalls": 864, + "mapProfileVisibleTriangles": 11376, + "nonAuthoritativeTestInstances": 72 + }, + "eligible": true, + "gameplayStatePerRenderInstance": false, + "onePrimitivePerMesh": true, + "selectedHighDetailMaxInstances": 1, + "staticRigid": true, + "strategy": "per-semantic-mesh" + }, + "loading": { + "abortable": true, + "allowedExtensions": [ + "KHR_materials_emissive_strength" + ], + "contentAddressFilenamePattern": "-.glb", + "contextRecoverySupported": true, + "credentials": "same-origin", + "embeddedOnly": true, + "exactBytesBeforeParse": true, + "exactSha256BeforeParse": true, + "idempotentDisposal": true, + "ordinaryBuildNetworkAccess": false, + "redirectsAllowed": false, + "releasePublicationRequiredBeforeUse": true, + "runtimeGitHubReleaseDependency": false, + "runtimeUseAuthorized": false, + "sameOriginOnly": true, + "timeoutCapMilliseconds": 60000, + "timeoutMilliseconds": 20000, + "transactionalInstall": true + }, + "motion": { + "authoredAnimations": [], + "continuousMotionRequired": false, + "default": "static", + "forbiddenClips": [ + "Attack", + "Walk", + "Death" + ], + "independentAnimationLoops": 0, + "mayEncodeGameplayState": false, + "phaseSource": "engine-supplied-public-instance-key", + "presentationOnly": true, + "reducedMotion": "static", + "scheduler": "existing-bounded-realm-scene-scheduler", + "staticWhen": [ + "reduced-quality", + "prefers-reduced-motion", + "strategic-overview", + "offscreen" + ], + "targets": [ + { + "maxVerticalMeters": 0.03, + "maxYawDegrees": 6, + "node": "CoreWatcher_SuspendedCore" + }, + { + "maxVerticalMeters": 0.02, + "maxYawDegrees": 4, + "node": "CoreWatcher_FloatingShard_1" + }, + { + "maxVerticalMeters": 0.02, + "maxYawDegrees": 4, + "node": "CoreWatcher_FloatingShard_2" + } + ] + }, + "presentation": { + "description": "A dormant Core Watcher presentation. Realm state and actions remain server-authoritative.", + "levelLabel": "Level 1", + "packageReviewArt": { + "bytes": 580483, + "decorative": true, + "height": 1600, + "packagePath": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png", + "runtimeUse": false, + "sha256": "bc20fa28239d8008b79f182509363a81b2bef6705fdf8436786ca70567e2cf9a", + "width": 1600 + }, + "previews": [ + { + "bytes": 193978, + "decorative": true, + "height": 1080, + "path": "previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg", + "runtimeUse": false, + "sha256": "d1e01453c620d22695148f89c18e9e5330c38508b1d984faae4037cb6d1dbcaa", + "width": 1920 + }, + { + "bytes": 278733, + "decorative": true, + "height": 1200, + "path": "previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg", + "runtimeUse": false, + "sha256": "d60510764ca2c57ae2bb67419e835cff3c7c4986bfe5f04156bd17687808e595", + "width": 2400 + }, + { + "bytes": 170456, + "decorative": true, + "height": 512, + "path": "previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png", + "runtimeUse": false, + "sha256": "64ae797aece8aa9594872b1265950f6a227baf0edface14b13217f749b0db774", + "width": 512 + } + ], + "shortLabel": "Watcher", + "uiSemanticsComeFromEngine": true + }, + "profiles": [ + { + "boundsGltfMeters": { + "max": [ + 0.855703592300415, + 2.14163864938768, + 0.8585932850837708 + ], + "min": [ + -0.7993891586294642, + 0.0, + -0.7494720541633232 + ], + "size": [ + 1.6550927509298794, + 2.14163864938768, + 1.6080653392470938 + ] + }, + "bytes": 129520, + "drawCalls": 23, + "embeddedBufferBytes": 110388, + "file": "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + "id": "high", + "materials": 3, + "meshes": 23, + "nodes": 24, + "onePrimitivePerMesh": true, + "partNodes": [ + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", + "CoreWatcher_CoreCage_2", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_2", + "CoreWatcher_FloatingShard_3", + "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", + "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", + "CoreWatcher_GroundFracture_4", + "CoreWatcher_GroundFracture_5", + "CoreWatcher_GroundFracture_6", + "CoreWatcher_GroundFracture_7", + "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", + "CoreWatcher_GroundShard_3", + "CoreWatcher_GroundShard_4", + "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore" + ], + "primitives": 23, + "rootNode": "Warpkeep_CoreWatcher_Level1_LOD0_High", + "sha256": "64218fe3082466ae5864885e7c7bd623e2c233d8f1e5231ca5e75979b9a045db", + "tier": "LOD0_High", + "triangles": 2354, + "uploadedVertices": 4210 + }, + { + "boundsGltfMeters": { + "max": [ + 0.7317647502528593, + 2.142855711951041, + 0.7091407179832458 + ], + "min": [ + -0.7993891586294642, + 0.0, + -0.7494720541633232 + ], + "size": [ + 1.5311539088823234, + 2.142855711951041, + 1.458612772146569 + ] + }, + "bytes": 67968, + "drawCalls": 20, + "embeddedBufferBytes": 50904, + "file": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + "id": "balanced", + "materials": 3, + "meshes": 20, + "nodes": 21, + "onePrimitivePerMesh": true, + "partNodes": [ + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", + "CoreWatcher_CoreCage_2", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_2", + "CoreWatcher_FloatingShard_3", + "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", + "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", + "CoreWatcher_GroundFracture_4", + "CoreWatcher_GroundFracture_5", + "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", + "CoreWatcher_GroundShard_3", + "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore" + ], + "primitives": 20, + "rootNode": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced", + "sha256": "f6112167a67d91db46e1e3dcabbea2e537b3f8c8bcb36de5caca1ec63170a1f6", + "tier": "LOD1_Balanced", + "triangles": 1000, + "uploadedVertices": 1972 + }, + { + "boundsGltfMeters": { + "max": [ + 0.7317647502528593, + 2.1436644242985894, + 0.7091407179832458 + ], + "min": [ + -0.7993891586294642, + 0.0, + -0.6200000047683716 + ], + "size": [ + 1.5311539088823234, + 2.1436644242985894, + 1.3291407227516174 + ] + }, + "bytes": 27636, + "drawCalls": 15, + "embeddedBufferBytes": 14340, + "file": "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + "id": "compact", + "materials": 3, + "meshes": 15, + "nodes": 16, + "onePrimitivePerMesh": true, + "partNodes": [ + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_2", + "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", + "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", + "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", + "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore" + ], + "primitives": 15, + "rootNode": "Warpkeep_CoreWatcher_Level1_LOD2_Compact", + "sha256": "6e9434d4e4f8ba03bec96a5bd47422caae88e04b230f7f3daf3219e833bf13e1", + "tier": "LOD2_Compact", + "triangles": 282, + "uploadedVertices": 552 + }, + { + "boundsGltfMeters": { + "max": [ + 0.6449754041834016, + 2.1446314543004137, + 0.7091407179832458 + ], + "min": [ + -0.7870411055325282, + 0.0, + -0.6200000047683716 + ], + "size": [ + 1.4320165097159299, + 2.1446314543004137, + 1.3291407227516174 + ] + }, + "bytes": 18748, + "drawCalls": 12, + "embeddedBufferBytes": 7740, + "file": "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + "id": "map", + "materials": 3, + "meshes": 12, + "nodes": 13, + "onePrimitivePerMesh": true, + "partNodes": [ + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_2", + "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", + "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundShard_1", + "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore" + ], + "primitives": 12, + "rootNode": "Warpkeep_CoreWatcher_Level1_LOD3_Map", + "sha256": "2f2e30bdd7593440cba8a5bd7a676d812c0b0a5910ece5174c8af434a72f146b", + "tier": "LOD3_Map", + "triangles": 158, + "uploadedVertices": 300 + } + ], + "qualityCamera": { + "allocatorOwnsFinalChoice": true, + "beyondFarBand": "engine-marker", + "distanceHintsMeters": { + "balancedThrough": 18, + "compactThrough": 36, + "highThrough": 8, + "mapThrough": 72 + }, + "hysteresisRequired": true, + "modelPresentationNeverGatesDiscoverability": true, + "packageReducedQualityHintDisposition": "superseded-here-by-no-optional-fetch; LOD2 remains available to non-reduced camera allocation", + "policies": { + "balanced": { + "closeSelected": "LOD1_Balanced", + "optionalAssetFetch": true, + "overview": "LOD3_Map", + "strategy": "LOD2_Compact" + }, + "high": { + "closeSelected": "LOD0_High", + "optionalAssetFetch": true, + "overview": "LOD3_Map", + "strategy": "LOD2_Compact" + }, + "reduced": { + "closeSelected": "engine-marker", + "optionalAssetFetch": false, + "overview": "engine-marker", + "strategy": "engine-marker" + } + }, + "semanticMarkerAlwaysMountedForValidInFramePublicRecord": true, + "strategicOverviewPresentationPointerInert": true + }, + "schema": "warpkeep.asset-integration-profile.v1", + "selectionAndGestures": { + "distantColliderCannotWin": true, + "engineOwnsPicking": true, + "focusRestoredAfterClose": true, + "mapZoomCadenceIndependent": true, + "mapZoomCumulativePerGesture": true, + "minimumControlCssPixels": 44, + "mustNotCaptureMapPanPinchOrWheel": true, + "presentationMeshesPointerInertInOverview": true, + "preferredControlCssPixels": 48, + "resetGestureStateOn": [ + "pointer-up", + "pointer-cancel", + "lost-pointer-capture", + "viewport-change", + "visibility-change" + ], + "sharedPickArbitrationRequired": true + }, + "status": { + "activationAuthorized": false, + "gameplayImplemented": false, + "integrated": false, + "releasePublished": false, + "reviewOnly": true, + "runtimeUseAuthorized": false + }, + "telemetryAndPrivacy": { + "aggregateFields": [ + "public-records-visible", + "semantic-markers-rendered", + "models-rendered-by-profile", + "instanced-draw-groups", + "visible-triangles", + "asset-bytes-loaded", + "fallback-count", + "context-loss-count", + "bounded-retry-count" + ], + "clientTelemetryImplementedHere": false, + "privateCoordinatesAllowed": false, + "privateIdentifiersAllowed": false, + "privateStateAllowedInAltTextOrDom": false, + "privateWorldAtlasAllowed": false + }, + "version": "1.0.0" +} diff --git a/contracts/core-watcher-level1-2026-08-03.runtime-manifest.json b/contracts/core-watcher-level1-2026-08-03.runtime-manifest.json new file mode 100644 index 0000000..2876020 --- /dev/null +++ b/contracts/core-watcher-level1-2026-08-03.runtime-manifest.json @@ -0,0 +1,307 @@ +{ + "assetId": "warpkeep.encounters.core.watcher.level1", + "authoringCoordinateSystem": "Blender, right-handed, +Z up, -Y front", + "authorityBoundary": { + "ai": false, + "collision": false, + "combat": false, + "damage": false, + "health": false, + "ownership": false, + "picking": false, + "placement": false, + "respawn": false, + "rewards": false, + "routing": false, + "spacetimeDb": false, + "visualOnly": true + }, + "category": "Encounters/Core/WatcherLevel1", + "combatEnabled": false, + "coordinateSystem": "glTF 2.0, right-handed, +Y up, +Z forward", + "encounterLevel": 1, + "enemyKind": "core-watcher", + "faction": "The Core", + "frontFacing": "+Z in glTF / -Y in Blender", + "lodGuidance": { + "LOD0_High": "selected inspection and close Realm zoom", + "LOD1_Balanced": "nearby normal-quality Realm view", + "LOD2_Compact": "medium distance and reduced-quality selected view", + "LOD3_Map": "far map signal and static reduced-motion presentation", + "suggestedDistancesMeters": { + "LOD0_HighThrough": 8, + "LOD1_BalancedThrough": 18, + "LOD2_CompactThrough": 36, + "LOD3_MapThrough": 72 + } + }, + "lods": [ + { + "animations": [], + "boundsBlender": { + "max": [ + 0.855704, + 0.795958, + 2.178009 + ], + "min": [ + -0.842088, + -0.858593, + 0.0 + ], + "size": [ + 1.697791, + 1.654551, + 2.178009 + ] + }, + "bytes": 129520, + "cameras": 0, + "embeddedBufferBytes": 110388, + "extensionsUsed": [ + "KHR_materials_emissive_strength" + ], + "externalUris": [], + "file": "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + "images": 0, + "materials": 3, + "meshes": 23, + "nodes": 24, + "primitives": 23, + "rigged": false, + "samplers": 0, + "scenes": 1, + "sha256": "64218fe3082466ae5864885e7c7bd623e2c233d8f1e5231ca5e75979b9a045db", + "skins": 0, + "textures": 0, + "tier": "LOD0_High", + "triangles": 2354, + "uploadedVertices": 4210 + }, + { + "animations": [], + "boundsBlender": { + "max": [ + 0.792875, + 0.795958, + 2.178009 + ], + "min": [ + -0.842088, + -0.709141, + 0.0 + ], + "size": [ + 1.634963, + 1.505098, + 2.178009 + ] + }, + "bytes": 67968, + "cameras": 0, + "embeddedBufferBytes": 50904, + "extensionsUsed": [ + "KHR_materials_emissive_strength" + ], + "externalUris": [], + "file": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + "images": 0, + "materials": 3, + "meshes": 20, + "nodes": 21, + "primitives": 20, + "rigged": false, + "samplers": 0, + "scenes": 1, + "sha256": "f6112167a67d91db46e1e3dcabbea2e537b3f8c8bcb36de5caca1ec63170a1f6", + "skins": 0, + "textures": 0, + "tier": "LOD1_Balanced", + "triangles": 1000, + "uploadedVertices": 1972 + }, + { + "animations": [], + "boundsBlender": { + "max": [ + 0.792875, + 0.62, + 2.178009 + ], + "min": [ + -0.842088, + -0.709141, + 0.0 + ], + "size": [ + 1.634963, + 1.329141, + 2.178009 + ] + }, + "bytes": 27636, + "cameras": 0, + "embeddedBufferBytes": 14340, + "extensionsUsed": [ + "KHR_materials_emissive_strength" + ], + "externalUris": [], + "file": "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + "images": 0, + "materials": 3, + "meshes": 15, + "nodes": 16, + "primitives": 15, + "rigged": false, + "samplers": 0, + "scenes": 1, + "sha256": "6e9434d4e4f8ba03bec96a5bd47422caae88e04b230f7f3daf3219e833bf13e1", + "skins": 0, + "textures": 0, + "tier": "LOD2_Compact", + "triangles": 282, + "uploadedVertices": 552 + }, + { + "animations": [], + "boundsBlender": { + "max": [ + 0.711313, + 0.62, + 2.179044 + ], + "min": [ + -0.841894, + -0.709141, + 0.0 + ], + "size": [ + 1.553207, + 1.329141, + 2.179044 + ] + }, + "bytes": 18748, + "cameras": 0, + "embeddedBufferBytes": 7740, + "extensionsUsed": [ + "KHR_materials_emissive_strength" + ], + "externalUris": [], + "file": "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + "images": 0, + "materials": 3, + "meshes": 12, + "nodes": 13, + "primitives": 12, + "rigged": false, + "samplers": 0, + "scenes": 1, + "sha256": "2f2e30bdd7593440cba8a5bd7a676d812c0b0a5910ece5174c8af434a72f146b", + "skins": 0, + "textures": 0, + "tier": "LOD3_Map", + "triangles": 158, + "uploadedVertices": 300 + } + ], + "materialContract": { + "alphaBlendMaterials": 0, + "authoringNote": { + "note": "Blender glTF export normalizes emissive color and strength; the material records above are the emitted runtime values.", + "runtimeValuesDerivedFromExportedGlbs": true, + "ultravioletNodeEmissionStrength": 3.5 + }, + "heraldry": "none", + "images": 0, + "materials": [ + { + "alphaMode": "OPAQUE", + "baseColorFactor": [ + 0.008, + 0.01, + 0.018, + 1.0 + ], + "doubleSided": false, + "emissiveFactor": [ + 0.0, + 0.0, + 0.0 + ], + "emissiveStrength": 0.0, + "metallic": 0.28, + "name": "WK_Core_Obsidian", + "opaque": true, + "roughness": 0.26 + }, + { + "alphaMode": "OPAQUE", + "baseColorFactor": [ + 0.022, + 0.025, + 0.04, + 1.0 + ], + "doubleSided": false, + "emissiveFactor": [ + 0.0, + 0.0, + 0.0 + ], + "emissiveStrength": 0.0, + "metallic": 0.82, + "name": "WK_Core_BlackenedMetal", + "opaque": true, + "roughness": 0.22 + }, + { + "alphaMode": "OPAQUE", + "baseColorFactor": [ + 0.065, + 0.018, + 0.22, + 1.0 + ], + "doubleSided": true, + "emissiveFactor": [ + 0.2564103, + 0.0576923, + 1.0 + ], + "emissiveStrength": 2.7299999, + "metallic": 0.08, + "name": "WK_Core_Ultraviolet", + "opaque": true, + "roughness": 0.18 + } + ], + "palette": "obsidian, blackened metal, restrained cold ultraviolet", + "textures": 0 + }, + "metersPerUnit": 1.0, + "motion": { + "animations": [], + "continuousMotionRequired": false, + "forbiddenClips": [ + "Attack", + "Walk", + "Death" + ], + "mode": "bounded-runtime-rigid-hierarchy", + "reducedMotion": "static", + "skins": 0 + }, + "name": "Core Watcher", + "pivot": "footprint center on Blender Z=0 / glTF Y=0", + "revision": "genesis-001-core-watcher-level1-2026-08-03", + "schema": "warpkeep.runtime-encounter-asset.v1", + "selectionGuidance": { + "presentationFootprintRadiusMeters": 0.9, + "renderGeometryIsAuthoritativeCollision": false, + "suggestedPickCylinderHeightMeters": 2.55, + "suggestedPickCylinderRadiusMeters": 0.72 + }, + "state": "dormant-presence", + "version": "1.0.0" +} diff --git a/manifests/core-watcher-level1-2026-08-03.source.json b/manifests/core-watcher-level1-2026-08-03.source.json new file mode 100644 index 0000000..f45b5b6 --- /dev/null +++ b/manifests/core-watcher-level1-2026-08-03.source.json @@ -0,0 +1,76 @@ +{ + "attachments": [ + { + "bytes": 1405757, + "entryCount": 15, + "mediaType": "application/zip", + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "packageRoot": "Warpkeep_CoreWatcher_Level1_GameReady", + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33", + "url": "https://github.com/ael-dev3/Warpkeep-Assets/releases/download/core-watcher-level1-2026-08-03/warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip" + } + ], + "authorization": "Preparation and public draft-PR review authorized; release attachment publication remains owner-gated.", + "gallery": { + "directory": "previews/core-watcher-level1-2026-08-03", + "images": 3 + }, + "packages": [ + { + "bytes": 2323903, + "category": "Encounters/Core/WatcherLevel1", + "combatEnabled": false, + "editableBlends": 1, + "encounterLevel": 1, + "enemyKind": "core-watcher", + "files": 15, + "jpgs": 2, + "jsonDocuments": 3, + "lods": [ + "LOD0_High", + "LOD1_Balanced", + "LOD2_Compact", + "LOD3_Map" + ], + "lodsPerWatcher": 4, + "motion": "static GLBs; optional bounded runtime rigid motion; reduced motion static", + "package": "Warpkeep_CoreWatcher_Level1_GameReady", + "pngs": 2, + "productionGLBs": 4, + "qaChecksPassed": 58, + "qaChecksTotal": 58, + "title": "Warpkeep Core Watcher \u2014 Level 1", + "triangleTotals": { + "LOD0_High": 2354, + "LOD1_Balanced": 1000, + "LOD2_Compact": 282, + "LOD3_Map": 158 + }, + "version": "1.0.0" + } + ], + "publicInventory": { + "bytes": 2323903, + "files": 15, + "packages": 1 + }, + "sanitization": { + "blendSemanticFingerprintsPreserved": true, + "blenderFilesReopened": 1, + "credentialsFound": false, + "localPathsRemoved": true, + "previewMetadataStripped": true, + "report": "reports/core-watcher-level1-2026-08-03/public-sanitization.json", + "runtimeGLBsByteExact": true, + "sourceSemanticFingerprintSha256": "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c" + }, + "schemaVersion": 1, + "snapshotDate": "2026-08-03", + "source": "Ael-directed original Warpkeep Core Watcher Level 1 production package", + "sourceInventory": { + "bytes": 2323903, + "files": 15, + "packages": 1 + }, + "tag": "core-watcher-level1-2026-08-03" +} diff --git a/previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg b/previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg new file mode 100644 index 0000000..38912dd Binary files /dev/null and b/previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg differ diff --git a/previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg b/previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg new file mode 100644 index 0000000..e3c15fa Binary files /dev/null and b/previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg differ diff --git a/previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png b/previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png new file mode 100644 index 0000000..a346f22 Binary files /dev/null and b/previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png differ diff --git a/previews/core-watcher-level1-2026-08-03/gallery.json b/previews/core-watcher-level1-2026-08-03/gallery.json new file mode 100644 index 0000000..74fb9a0 --- /dev/null +++ b/previews/core-watcher-level1-2026-08-03/gallery.json @@ -0,0 +1,31 @@ +{ + "images": [ + { + "bytes": 193978, + "dimensions": "1920x1080", + "file": "00-core-watcher-presentation.jpg", + "sha256": "d1e01453c620d22695148f89c18e9e5330c38508b1d984faae4037cb6d1dbcaa", + "source": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg", + "title": "Core Watcher Level 1 presentation" + }, + { + "bytes": 278733, + "dimensions": "2400x1200", + "file": "01-core-watcher-lod-lineup.jpg", + "sha256": "d60510764ca2c57ae2bb67419e835cff3c7c4986bfe5f04156bd17687808e595", + "source": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg", + "title": "Core Watcher four-LOD comparison" + }, + { + "bytes": 170456, + "dimensions": "512x512", + "file": "02-core-watcher-map-preview.png", + "sha256": "64ae797aece8aa9594872b1265950f6a227baf0edface14b13217f749b0db774", + "source": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png", + "title": "Core Watcher far-map preview" + } + ], + "releaseTag": "core-watcher-level1-2026-08-03", + "schema": "warpkeep.preview-gallery.v1", + "status": "release-candidate-not-published" +} diff --git a/provenance/core-watcher-level1-2026-08-03.md b/provenance/core-watcher-level1-2026-08-03.md new file mode 100644 index 0000000..1adb710 --- /dev/null +++ b/provenance/core-watcher-level1-2026-08-03.md @@ -0,0 +1,123 @@ +# Warpkeep Core Watcher — Level 1 release candidate + +- **Candidate tag:** `core-watcher-level1-2026-08-03` +- **Snapshot date:** 2026-08-03 +- **Supplied by:** Ael-directed original Warpkeep production +- **Review authority:** Ael explicitly authorized preparing a new, isolated draft PR in `ael-dev3/Warpkeep-Assets`. +- **Designation:** game-ready asset release candidate; not published, integrated, deployed, activated, or merged + +## Scope + +The prepared archive contains one original Level 1 Core Watcher with: + +- one editable Blender 5.2 source; +- four static runtime GLBs: High, Balanced, Compact, and Map; +- three texture-free materials: obsidian, blackened metal, and restrained cold ultraviolet; +- presentation, transparent, four-LOD, and mobile map renders; +- authoring and runtime manifests, 58-check QA evidence, and nested checksums; and +- an archive-only package notice that preserves the no-separate-open-license boundary. + +The Watcher is a narrow bifurcated monolith with a suspended core and +asymmetric floating shards. It has no human face, legs, weapon, gun, wings, +banner, heraldry, walk cycle, attack clip, or death clip. Render geometry is +visual only and does not define placement, picking, collision, combat, health, +damage, rewards, respawn, AI, ownership, routing, or SpacetimeDB authority. + +## Production history and originality + +The geometry, materials, LODs, renders, manifests, and package tooling were +substantially authored and prepared with Codex under Ael's direction and +review using Blender 5.2. No model, texture, crest, or geometry was imported +from another Warpkeep release or from a third-party reference. + +Broad world-map research informed only general presentation goals: low-level +neutral encounters should remain recognizable at map scale, searchable by +level or type, and visually distinct from owned structures. The review set +included [Rise of Kingdoms neutral-unit guidance](https://riseofkingdomsguides.com/rise-of-kingdoms-barbarians-and-barbarian-forts/), +[Call of Dragons neutral-unit guidance](https://cod.guide/darkling-units/), and +the [Game of Thrones: Conquest Map Finder description](https://hbogamessupport.wbgames.com/hc/en-us/articles/360001992207-The-Map-Finder). +Those sources supplied no copied art, names, layout, code, textures, or model +data and are not included in the candidate archive. + +The separate Core faction crest release and its earlier branch or PR were +explicitly kept outside this package. No Core crest, Hegemony crest, palette +swap, or Hegemony asset was used. The resulting Watcher geometry and +three-material treatment are original to this candidate. + +## Public-copy preparation + +The four runtime GLBs are self-contained glTF 2.0 files with embedded buffers, +no external URI, no image or texture, one scene, three named materials, and no +camera, light, skin, animation, or executable content. Export-time UV data was +omitted because the model is texture-free. + +All four Blender-rendered preview frames were decoded and re-encoded with +Pillow 11.3.0 without comments, EXIF/XMP, PNG text/time chunks, or other source +metadata. The exact public copies contain no workstation or temporary build +path. No generative alteration was applied during this metadata-only step. + +Blender's headless Save As operation serialized one workstation home-directory +string in transient file-browser UI state. The public candidate neutralized +that fixed-size UI string in place, then reopened the source in Blender 5.2 and +confirmed the same topology, transforms, material values, authority metadata, +and semantic fingerprint. No model data was changed by sanitization. + +Every nested checksum passed. The generic release verifier accepted the exact +1,405,757-byte candidate ZIP and its 15-entry manifest. The official Khronos +glTF validator reported zero issues for all four GLBs, and each file imported +cleanly into a separate factory Blender session. A second full build produced +byte-identical GLBs and previews without descriptive or private metadata, plus +the same editable-source semantic fingerprint. A disabled-autoexec source audit +also found no embedded text scripts, scripted drivers, actions, or external +libraries or media. Exact results are recorded in the [external validation +report](../reports/core-watcher-level1-2026-08-03/external-validation.json). + +## Distribution shape and gate + +The large `.blend`, `.glb`, transparent master, and ZIP remain outside normal +Git history. This draft PR tracks only the candidate manifest and checksum, +source inventory, provenance, package notice, sanitization/validation evidence, +three exact rendered previews, and focused verification software. + +The manifest records the intended immutable release coordinates so reviewers +can audit the complete shape. The ZIP has **not** been uploaded, the intended +tag has **not** been created, and no GitHub Release exists for this candidate. +Publication, integration into the main Warpkeep repository, world placement, +combat behavior, deployment, activation, merging, and all later PR slices +remain separate owner-gated decisions. + +## Modern integration review — 2026-08-08 + +The candidate was re-audited against Warpkeep `main` at +`e8bd06553bc11cd50842edb4812492b050c02cdb` and the public shape of the +outstanding Realm visual, Inner Keep, chat/admission, and continent-atlas draft +work. That inspection was read-only. No code, art, coordinates, seeds, hidden +sites, review images, or private atlas data were copied from another PR, and +no review, edit, comment, merge, or other state-changing action was submitted +to another PR. No outstanding PR was made a dependency of this candidate. + +The resulting tracked integration profile records only asset-facing +constraints that a later implementation must honor: immutable bytes and full +digests; same-origin, redirect-denied, abortable loading; embedded-only GLB +validation; High/Balanced/Reduced behavior; shared-scheduler presentation +motion; component-wise instancing; mobile pointer and cumulative-pinch +coexistence; semantic 44–48px controls; history/back and focus restoration; +procedural fallback; and aggregate-only telemetry. The byte-exact packaged +runtime manifest is also tracked separately so reviewers can inspect it +without access to the unpublished archive. + +The profile's 72-instance calculation is a renderer capacity exercise, not a +population or placement assertion. It contains no world coordinate, site ID, +seed, combat value, ownership record, route, action, or SpacetimeDB schema. +Catalogue/placement, client rendering and selection, authoritative PvE state, +operations, and activation remain later B–F slices, each marked unimplemented +and unauthorized here. + +## License boundary + +Public draft-PR review of this named candidate's lightweight metadata and +previews was authorized by Ael. No separate open-license grant is asserted or +inferred. This review authorization does not license third-party tools or +services, third-party reference material, Warpkeep trademarks or canonical +identity, the separate Core faction crest release, or unrelated Warpkeep +material. diff --git a/releases/core-watcher-level1-2026-08-03/PACKAGE-NOTICE.md b/releases/core-watcher-level1-2026-08-03/PACKAGE-NOTICE.md new file mode 100644 index 0000000..a2ac252 --- /dev/null +++ b/releases/core-watcher-level1-2026-08-03/PACKAGE-NOTICE.md @@ -0,0 +1,18 @@ +# Public archive candidate notice + +This named Warpkeep Core Watcher Level 1 package was prepared under Ael's +direction for public review in a dedicated Warpkeep-Assets draft pull request. + +Public release attachment publication, tag creation, runtime integration, +deployment, and gameplay activation remain separately owner-gated. + +No separate open-source or open-content license grant is made by this notice. +Copyright, trademark, and other rights remain with their respective owners. + +The model, LODs, renders, manifests, and QA were substantially authored and +prepared with Codex under Ael's direction and review using Blender 5.2. No +Hegemony model, crest, third-party model, or protected reference artwork was +used as geometry or texture input. + +This is a visual production-asset handoff only. It implements no combat, +health, damage, rewards, respawn, AI, ownership, routing, or world authority. diff --git a/releases/core-watcher-level1-2026-08-03/SHA256SUMS.txt b/releases/core-watcher-level1-2026-08-03/SHA256SUMS.txt new file mode 100644 index 0000000..1b1422d --- /dev/null +++ b/releases/core-watcher-level1-2026-08-03/SHA256SUMS.txt @@ -0,0 +1 @@ +34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33 warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip diff --git a/releases/core-watcher-level1-2026-08-03/manifest.json b/releases/core-watcher-level1-2026-08-03/manifest.json new file mode 100644 index 0000000..6822516 --- /dev/null +++ b/releases/core-watcher-level1-2026-08-03/manifest.json @@ -0,0 +1,156 @@ +{ + "attachments": [ + { + "bytes": 1405757, + "entries": [ + { + "bytes": 901, + "compressedBytes": 522, + "mediaType": "text/markdown", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/PACKAGE-NOTICE.md", + "sha256": "393e9b31e13a1afbf1f841003a0fbd16bec573480e0dc8a9a7c972784e933ad8" + }, + { + "bytes": 170456, + "compressedBytes": 170511, + "mediaType": "image/png", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png", + "sha256": "64ae797aece8aa9594872b1265950f6a227baf0edface14b13217f749b0db774" + }, + { + "bytes": 278733, + "compressedBytes": 271292, + "mediaType": "image/jpeg", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg", + "sha256": "d60510764ca2c57ae2bb67419e835cff3c7c4986bfe5f04156bd17687808e595" + }, + { + "bytes": 193978, + "compressedBytes": 190285, + "mediaType": "image/jpeg", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg", + "sha256": "d1e01453c620d22695148f89c18e9e5330c38508b1d984faae4037cb6d1dbcaa" + }, + { + "bytes": 580483, + "compressedBytes": 580454, + "mediaType": "image/png", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png", + "sha256": "bc20fa28239d8008b79f182509363a81b2bef6705fdf8436786ca70567e2cf9a" + }, + { + "bytes": 5429, + "compressedBytes": 788, + "mediaType": "application/json", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/QA/Warpkeep_CoreWatcher_Level1_RuntimeQA.json", + "sha256": "4ba14d252674c31f6fb3f2f84654fc0bc661a734abb58b2640f9d1006211e139" + }, + { + "bytes": 1500, + "compressedBytes": 875, + "mediaType": "text/markdown", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/README.md", + "sha256": "fdcebb4aa3c87b9a51a6f741963039d9ebe62947a3304b944fdc316350bd5826" + }, + { + "bytes": 129520, + "compressedBytes": 25223, + "mediaType": "model/gltf-binary", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + "sha256": "64218fe3082466ae5864885e7c7bd623e2c233d8f1e5231ca5e75979b9a045db" + }, + { + "bytes": 67968, + "compressedBytes": 12502, + "mediaType": "model/gltf-binary", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + "sha256": "f6112167a67d91db46e1e3dcabbea2e537b3f8c8bcb36de5caca1ec63170a1f6" + }, + { + "bytes": 27636, + "compressedBytes": 5821, + "mediaType": "model/gltf-binary", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + "sha256": "6e9434d4e4f8ba03bec96a5bd47422caae88e04b230f7f3daf3219e833bf13e1" + }, + { + "bytes": 18748, + "compressedBytes": 4131, + "mediaType": "model/gltf-binary", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + "sha256": "2f2e30bdd7593440cba8a5bd7a676d812c0b0a5910ece5174c8af434a72f146b" + }, + { + "bytes": 7462, + "compressedBytes": 2018, + "mediaType": "application/json", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/WatcherLevel1/runtime-manifest.json", + "sha256": "0339dc9abe5c6a9340ef9be1d0ad908a5130eb9339a7ace4fafdb25a7548d1fd" + }, + { + "bytes": 1716, + "compressedBytes": 822, + "mediaType": "text/plain", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/SHA256SUMS.txt", + "sha256": "dc5b32a82f9025a84516a738eaa3a509738991e3d295705eb5ecb488205fef3f" + }, + { + "bytes": 837223, + "compressedBytes": 135631, + "mediaType": "application/x-blender", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/Source/Warpkeep_CoreWatcher_Level1_Editable.blend", + "sha256": "3989b9899c5c2522ecf8686646008c87beb168eefc3aaeab1b3f9bbeb7c0933d" + }, + { + "bytes": 2150, + "compressedBytes": 996, + "mediaType": "application/json", + "path": "Warpkeep_CoreWatcher_Level1_GameReady/asset-manifest.json", + "sha256": "700ad4f03c09490a532be26c7b8f468bbc983c7ed89db63dfc73a3370f670622" + } + ], + "mediaType": "application/zip", + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "packageRoot": "Warpkeep_CoreWatcher_Level1_GameReady", + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33", + "url": "https://github.com/ael-dev3/Warpkeep-Assets/releases/download/core-watcher-level1-2026-08-03/warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip" + } + ], + "designation": { + "name": "Warpkeep Core Watcher Level 1 GameReady", + "status": "release-candidate; not published or integrated" + }, + "inventory": { + "bytes": 2323903, + "editableBlends": 1, + "files": 15, + "jsonDocuments": 3, + "packageRoot": "Warpkeep_CoreWatcher_Level1_GameReady", + "previews": 4, + "runtimeGLBs": 4 + }, + "license": { + "scope": "Named Core Watcher candidate package only; release, runtime, trademark, and canonical identity remain separately gated.", + "spdx": null, + "status": "public-draft-review-authorized-no-separate-open-license" + }, + "repository": "ael-dev3/Warpkeep-Assets", + "schemaVersion": 1, + "source": { + "authorization": "Preparation and public draft-PR review authorized; release publication remains owner-gated.", + "creationDisclosure": "Substantially authored and prepared with Codex under Ael direction using Blender 5.2; preview metadata stripped with Pillow 11.3.0.", + "privateWorkflowMetadata": "excluded", + "suppliedBy": "Ael-directed original Warpkeep production" + }, + "tag": "core-watcher-level1-2026-08-03", + "verification": { + "archives": "One deterministic safe-path ZIP; exact entry order, timestamps, modes, byte counts, SHA-256 values, and compression ratios verified.", + "blender": "The editable Blender source reopened in Blender 5.2 with a matching topology, coordinate, transform, material, and authority-boundary fingerprint.", + "checksums": "Every nested package checksum passed before deterministic packaging.", + "images": "All previews were rendered by Blender, decoded and re-encoded with Pillow 11.3.0 without metadata, and contain no external source dependency.", + "privacy": "No credential, private author path, symlink, executable, unsafe path, external library, image texture, or remote dependency is present.", + "qa": "The supplied runtime QA report records 58/58 checks passed.", + "runtime": "All four GLBs passed deep header/chunk/buffer/URI/geometry/material/extension/bounds validation.", + "runtimeIntegration": "No current in-game integration, release, deployment, combat, or activation is asserted." + } +} diff --git a/reports/core-watcher-level1-2026-08-03/external-validation.json b/reports/core-watcher-level1-2026-08-03/external-validation.json new file mode 100644 index 0000000..183bb62 --- /dev/null +++ b/reports/core-watcher-level1-2026-08-03/external-validation.json @@ -0,0 +1,141 @@ +{ + "archive": { + "bytes": 1405757, + "entryCount": 15, + "genericReleaseVerifier": "passed", + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "published": false, + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33" + }, + "blender": { + "cleanFactoryImports": [ + { + "actions": 0, + "armatures": 0, + "cameras": 0, + "file": "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + "lights": 0, + "materials": [ + "WK_Core_BlackenedMetal", + "WK_Core_Obsidian", + "WK_Core_Ultraviolet" + ], + "meshObjects": 23 + }, + { + "actions": 0, + "armatures": 0, + "cameras": 0, + "file": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + "lights": 0, + "materials": [ + "WK_Core_BlackenedMetal", + "WK_Core_Obsidian", + "WK_Core_Ultraviolet" + ], + "meshObjects": 20 + }, + { + "actions": 0, + "armatures": 0, + "cameras": 0, + "file": "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + "lights": 0, + "materials": [ + "WK_Core_BlackenedMetal", + "WK_Core_Obsidian", + "WK_Core_Ultraviolet" + ], + "meshObjects": 15 + }, + { + "actions": 0, + "armatures": 0, + "cameras": 0, + "file": "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + "lights": 0, + "materials": [ + "WK_Core_BlackenedMetal", + "WK_Core_Obsidian", + "WK_Core_Ultraviolet" + ], + "meshObjects": 12 + } + ], + "sourceReopened": true, + "sourceSafetyAudit": { + "actions": 0, + "autoExecutionEnabled": false, + "embeddedTextBlocks": 0, + "externalFonts": 0, + "externalImages": 0, + "externalLibraries": 0, + "externalMovieClips": 0, + "externalSounds": 0, + "scriptedDrivers": 0 + }, + "sourceSemanticFingerprintSha256": "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c", + "version": "5.2.0 LTS" + }, + "candidateTag": "core-watcher-level1-2026-08-03", + "gltfValidator": { + "implementation": "Khronos glTF Validator through glTF Transform CLI", + "issuesByFile": { + "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb": 0, + "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb": 0, + "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb": 0, + "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb": 0 + }, + "status": "passed", + "cliVersion": "4.4.1", + "validatorVersion": "2.0.0-dev.3.10" + }, + "lods": [ + { + "bytes": 129520, + "file": "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + "sha256": "64218fe3082466ae5864885e7c7bd623e2c233d8f1e5231ca5e75979b9a045db", + "tier": "LOD0_High", + "triangles": 2354, + "uploadedVertices": 4210 + }, + { + "bytes": 67968, + "file": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + "sha256": "f6112167a67d91db46e1e3dcabbea2e537b3f8c8bcb36de5caca1ec63170a1f6", + "tier": "LOD1_Balanced", + "triangles": 1000, + "uploadedVertices": 1972 + }, + { + "bytes": 27636, + "file": "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + "sha256": "6e9434d4e4f8ba03bec96a5bd47422caae88e04b230f7f3daf3219e833bf13e1", + "tier": "LOD2_Compact", + "triangles": 282, + "uploadedVertices": 552 + }, + { + "bytes": 18748, + "file": "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + "sha256": "2f2e30bdd7593440cba8a5bd7a676d812c0b0a5910ece5174c8af434a72f146b", + "tier": "LOD3_Map", + "triangles": 158, + "uploadedVertices": 300 + } + ], + "packageQa": { + "checksPassed": 58, + "checksTotal": 58, + "status": "passed" + }, + "rebuildComparison": { + "archiveByteIdentity": "not asserted; Blender container bytes are allowed to vary between clean authoring sessions", + "metadataFreePreviewsByteIdentical": true, + "runtimeGlbsByteIdentical": true, + "sourceSemanticFingerprintIdentical": true + }, + "schema": "warpkeep.external-asset-validation.v1", + "snapshotDate": "2026-08-03", + "status": "passed" +} diff --git a/reports/core-watcher-level1-2026-08-03/public-sanitization.json b/reports/core-watcher-level1-2026-08-03/public-sanitization.json new file mode 100644 index 0000000..cde333c --- /dev/null +++ b/reports/core-watcher-level1-2026-08-03/public-sanitization.json @@ -0,0 +1,125 @@ +{ + "archive": { + "bytes": 1405757, + "entryCount": 15, + "fileMode": "0644", + "fixedTimestamp": "2026-08-03T12:00:00", + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33" + }, + "authorization": { + "archiveOnlyNotice": "present inside package", + "creationDisclosure": "Codex-assisted original modeling and package preparation under Ael direction; Blender 5.2; preview metadata stripping with Pillow 11.3.0", + "license": "no separate open-license grant asserted", + "publicArchive": "draft PR review authorized; public release publication pending owner approval", + "runtimeIntegration": "not asserted" + }, + "blendAudits": [ + { + "fragmentsNeutralized": 1, + "path": "Source/Warpkeep_CoreWatcher_Level1_Editable.blend", + "privateStringsFound": [], + "publicSha256": "3989b9899c5c2522ecf8686646008c87beb168eefc3aaeab1b3f9bbeb7c0933d", + "reopened": true, + "semanticFingerprintPreserved": true, + "semanticFingerprintSha256": "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c" + } + ], + "designation": "Warpkeep Core Watcher Level 1 GameReady release candidate", + "filesystemNormalization": { + "directoryMode": "0755", + "fileMode": "0644", + "normalizedDirectories": 8, + "normalizedFiles": 15, + "removedExtendedAttributes": 0 + }, + "imageMetadataPolicy": "metadataFieldsFound records descriptive, private, or ancillary metadata; standard JPEG JFIF APP0 structure is reported separately and is not treated as descriptive metadata", + "imageAudits": [ + { + "bytes": 170456, + "metadataFieldsFound": [], + "path": "Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png", + "privateStringsFound": [], + "sanitizer": "Pillow 11.3.0 re-encode without ancillary, descriptive, or private metadata", + "sha256": "64ae797aece8aa9594872b1265950f6a227baf0edface14b13217f749b0db774" + }, + { + "bytes": 278733, + "metadataFieldsFound": [], + "path": "Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg", + "privateStringsFound": [], + "standardJfifStructure": { + "density": [ + 1, + 1 + ], + "unit": 0, + "version": "1.01" + }, + "sanitizer": "Pillow 11.3.0 re-encode without descriptive or private metadata; standard JFIF APP0 retained", + "sha256": "d60510764ca2c57ae2bb67419e835cff3c7c4986bfe5f04156bd17687808e595" + }, + { + "bytes": 193978, + "metadataFieldsFound": [], + "path": "Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg", + "privateStringsFound": [], + "standardJfifStructure": { + "density": [ + 1, + 1 + ], + "unit": 0, + "version": "1.01" + }, + "sanitizer": "Pillow 11.3.0 re-encode without descriptive or private metadata; standard JFIF APP0 retained", + "sha256": "d1e01453c620d22695148f89c18e9e5330c38508b1d984faae4037cb6d1dbcaa" + }, + { + "bytes": 580483, + "metadataFieldsFound": [], + "path": "Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png", + "privateStringsFound": [], + "sanitizer": "Pillow 11.3.0 re-encode without ancillary, descriptive, or private metadata", + "sha256": "bc20fa28239d8008b79f182509363a81b2bef6705fdf8436786ca70567e2cf9a" + } + ], + "inventory": { + "bytes": 2323903, + "credentialHits": [], + "files": 15, + "mediaCounts": { + ".blend": 1, + ".glb": 4, + ".jpg": 2, + ".json": 3, + ".md": 2, + ".png": 2, + ".txt": 1 + }, + "osMetadata": [], + "privatePathHits": [], + "unsafePaths": [] + }, + "passed": true, + "schema": "warpkeep.public-asset-sanitization.v1", + "snapshotDate": "2026-08-03", + "sourcePackage": "Warpkeep_CoreWatcher_Level1_GameReady", + "stagingRoot": "sanitized-build/core-watcher-level1", + "verification": { + "allGlbsSelfContained": true, + "allGlbsValidGltf2": true, + "allJsonParsed": true, + "allPreviewsRenderedByBlender": true, + "blendAuditedAndReopened": true, + "deterministicZipMetadata": true, + "nestedChecksumsPassed": true, + "noCredentials": true, + "noExecutables": true, + "noOsMetadata": true, + "noPrivatePaths": true, + "noSymlinks": true, + "noUnsafePaths": true, + "previewMetadataStripped": true + } +} diff --git a/scripts/verify_core_watcher_integration_profile.py b/scripts/verify_core_watcher_integration_profile.py new file mode 100755 index 0000000..b585945 --- /dev/null +++ b/scripts/verify_core_watcher_integration_profile.py @@ -0,0 +1,1145 @@ +#!/usr/bin/env python3 +"""Verify the tracked, review-only Core Watcher integration handoff. + +This verifier needs no unpublished release binary. It binds the advisory +renderer profile to the tracked release manifest, the byte-exact packaged +runtime-manifest copy, and review images re-encoded without descriptive or +private metadata. It does not authorize publication, integration, gameplay, or +activation. +""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.util +import json +import math +import os +from pathlib import Path, PurePosixPath, PureWindowsPath +import re +import stat +import sys +import unicodedata + + +PROFILE_PATH = Path( + "contracts/core-watcher-level1-2026-08-03.integration-profile.json" +) +RUNTIME_PATH = Path( + "contracts/core-watcher-level1-2026-08-03.runtime-manifest.json" +) +RELEASE_MANIFEST_PATH = Path("releases/core-watcher-level1-2026-08-03/manifest.json") +CHECKSUM_SIDECAR_PATH = Path( + "releases/core-watcher-level1-2026-08-03/SHA256SUMS.txt" +) +GALLERY_PATH = Path("previews/core-watcher-level1-2026-08-03/gallery.json") +PACKAGE_VERIFIER_PATH = Path("scripts/verify_core_watcher_level1.py") +SHA256_RE = re.compile(r"[0-9a-f]{64}") +ZERO_DIGEST = "0" * 64 +EXPECTED_PROFILE_DIGEST = "0a34614dfb42f754fd2524b23ef213c2db502768ad9230bd6a27a9198a8251c0" +MAX_PROFILE_BYTES = 256 * 1024 +MAX_TRACKED_JSON_BYTES = 2 * 1024 * 1024 +MAX_PREVIEW_BYTES = 8 * 1024 * 1024 +MAX_JSON_DEPTH = 64 +MAX_JSON_NUMBER_CHARS = 128 +MAX_TRACKED_PATH_CHARS = 512 +MAX_TRACKED_PATH_COMPONENTS = 16 +CANONICALIZATION = ( + "exact tracked UTF-8 bytes with the 64 lowercase hexadecimal characters at " + "$.contractDigest.sha256 replaced by 64 ASCII zeroes; no other transformation" +) + +ROOT_KEYS = { + "assetBinding", + "authorityBoundary", + "contractDigest", + "fallbackAndAccessibility", + "futureGameplaySlices", + "geometry", + "identity", + "instancing", + "loading", + "motion", + "presentation", + "profiles", + "qualityCamera", + "schema", + "selectionAndGestures", + "status", + "telemetryAndPrivacy", + "version", +} + +EXPECTED_STATUS = { + "activationAuthorized": False, + "gameplayImplemented": False, + "integrated": False, + "releasePublished": False, + "reviewOnly": True, + "runtimeUseAuthorized": False, +} +EXPECTED_AUTHORITY = { + "actions": False, + "activation": False, + "ai": False, + "catalog": False, + "collision": False, + "combat": False, + "combatState": False, + "cooldowns": False, + "damage": False, + "encounterState": False, + "fogOfWar": False, + "health": False, + "loot": False, + "networking": False, + "ownership": False, + "pathing": False, + "persistence": False, + "picking": False, + "placement": False, + "placementEligibility": False, + "populationCount": False, + "respawn": False, + "rewards": False, + "routing": False, + "selection": False, + "siteIdentity": False, + "spacetimeDb": False, + "visibility": False, + "visualOnly": True, + "worldCoordinates": False, +} +EXPECTED_LOADING = { + "abortable": True, + "allowedExtensions": ["KHR_materials_emissive_strength"], + "contentAddressFilenamePattern": "-.glb", + "contextRecoverySupported": True, + "credentials": "same-origin", + "embeddedOnly": True, + "exactBytesBeforeParse": True, + "exactSha256BeforeParse": True, + "idempotentDisposal": True, + "ordinaryBuildNetworkAccess": False, + "redirectsAllowed": False, + "releasePublicationRequiredBeforeUse": True, + "runtimeGitHubReleaseDependency": False, + "runtimeUseAuthorized": False, + "sameOriginOnly": True, + "timeoutCapMilliseconds": 60000, + "timeoutMilliseconds": 20000, + "transactionalInstall": True, +} +EXPECTED_RELEASE_BINDING = { + "bytes": 7520, + "sha256": "60eaa17e477d37f42d25b446ab9a907c6d57b919007b3632c3e5f327283113f0", + "trackedPath": "releases/core-watcher-level1-2026-08-03/manifest.json", +} +EXPECTED_CHECKSUM_SIDECAR_BYTES = 124 +EXPECTED_CHECKSUM_SIDECAR_SHA256 = ( + "8196227e0cf7b4cc66d39ab14f2508c5f3d865b54222ab4293440bc818314f09" +) +EXPECTED_GALLERY_BINDING = { + "bytes": 1255, + "sha256": "90ec48066c69c6a4223dbc3911784c08c6dc86cad686d3581f4cfcfd15cab6bc", + "trackedPath": "previews/core-watcher-level1-2026-08-03/gallery.json", +} +EXPECTED_RUNTIME_BINDING = { + "bytes": 7462, + "packagePath": ( + "Warpkeep_CoreWatcher_Level1_GameReady/Runtime/Encounters/Core/" + "WatcherLevel1/runtime-manifest.json" + ), + "sha256": "0339dc9abe5c6a9340ef9be1d0ad908a5130eb9339a7ace4fafdb25a7548d1fd", + "trackedPath": "contracts/core-watcher-level1-2026-08-03.runtime-manifest.json", +} +EXPECTED_SOURCE_BINDING = { + "bytes": 2416, + "sha256": "2c52e4744914b27fad6ba6b1e28ae1a02363612080079ac62933fd16ce580b04", + "trackedPath": "manifests/core-watcher-level1-2026-08-03.source.json", +} +EXPECTED_QUALITY = { + "allocatorOwnsFinalChoice": True, + "beyondFarBand": "engine-marker", + "distanceHintsMeters": { + "balancedThrough": 18, + "compactThrough": 36, + "highThrough": 8, + "mapThrough": 72, + }, + "hysteresisRequired": True, + "modelPresentationNeverGatesDiscoverability": True, + "packageReducedQualityHintDisposition": ( + "superseded-here-by-no-optional-fetch; LOD2 remains available to " + "non-reduced camera allocation" + ), + "policies": { + "balanced": { + "closeSelected": "LOD1_Balanced", + "optionalAssetFetch": True, + "overview": "LOD3_Map", + "strategy": "LOD2_Compact", + }, + "high": { + "closeSelected": "LOD0_High", + "optionalAssetFetch": True, + "overview": "LOD3_Map", + "strategy": "LOD2_Compact", + }, + "reduced": { + "closeSelected": "engine-marker", + "optionalAssetFetch": False, + "overview": "engine-marker", + "strategy": "engine-marker", + }, + }, + "semanticMarkerAlwaysMountedForValidInFramePublicRecord": True, + "strategicOverviewPresentationPointerInert": True, +} +EXPECTED_SELECTION = { + "distantColliderCannotWin": True, + "engineOwnsPicking": True, + "focusRestoredAfterClose": True, + "mapZoomCadenceIndependent": True, + "mapZoomCumulativePerGesture": True, + "minimumControlCssPixels": 44, + "mustNotCaptureMapPanPinchOrWheel": True, + "presentationMeshesPointerInertInOverview": True, + "preferredControlCssPixels": 48, + "resetGestureStateOn": [ + "pointer-up", + "pointer-cancel", + "lost-pointer-capture", + "viewport-change", + "visibility-change", + ], + "sharedPickArbitrationRequired": True, +} +EXPECTED_MOTION = { + "authoredAnimations": [], + "continuousMotionRequired": False, + "default": "static", + "forbiddenClips": ["Attack", "Walk", "Death"], + "independentAnimationLoops": 0, + "mayEncodeGameplayState": False, + "phaseSource": "engine-supplied-public-instance-key", + "presentationOnly": True, + "reducedMotion": "static", + "scheduler": "existing-bounded-realm-scene-scheduler", + "staticWhen": [ + "reduced-quality", + "prefers-reduced-motion", + "strategic-overview", + "offscreen", + ], + "targets": [ + {"maxVerticalMeters": 0.03, "maxYawDegrees": 6, "node": "CoreWatcher_SuspendedCore"}, + {"maxVerticalMeters": 0.02, "maxYawDegrees": 4, "node": "CoreWatcher_FloatingShard_1"}, + {"maxVerticalMeters": 0.02, "maxYawDegrees": 4, "node": "CoreWatcher_FloatingShard_2"}, + ], +} +EXPECTED_FALLBACK = { + "accessibleName": "Level 1 Core Watcher", + "cameraNeutralInspect": True, + "completeSemanticExploreListAlwaysAvailable": True, + "engineGenerated": True, + "fallbackShape": "bifurcated-spire-marker", + "historyBackClosesInspectionBeforeLeavingRealm": True, + "locateIsTheOnlyCameraMovingAction": True, + "modelFailurePreservesPublicRecord": True, + "opaqueIdentifiersExcludedFromDomAndAltText": True, + "presentationArtDecorative": True, + "preserveSelection": True, + "required": True, + "retryPolicy": "client-owned-bounded-no-retry-storm", + "semanticDataSource": "engine-and-server-public-projection", + "testMatrix": { + "keyboardAndScreenReader": True, + "pageZoomPercent": 200, + "prefersReducedMotion": True, + "safariIphone": { + "pageScroll": True, + "pinchZoom": True, + "safeAreaInsets": True, + "visualViewportResize": True, + }, + "viewportCssPixels": [390, 844], + }, +} +EXPECTED_TELEMETRY = { + "aggregateFields": [ + "public-records-visible", + "semantic-markers-rendered", + "models-rendered-by-profile", + "instanced-draw-groups", + "visible-triangles", + "asset-bytes-loaded", + "fallback-count", + "context-loss-count", + "bounded-retry-count", + ], + "clientTelemetryImplementedHere": False, + "privateCoordinatesAllowed": False, + "privateIdentifiersAllowed": False, + "privateStateAllowedInAltTextOrDom": False, + "privateWorldAtlasAllowed": False, +} +EXPECTED_FUTURE_SLICES = [ + { + "implementedHere": False, + "owner": "Warpkeep public catalogue and server placement authority", + "slice": "B", + "surface": ( + "public Core site identities, eligible cells, selected-world topology, " + "hydrology, and visibility projection" + ), + }, + { + "implementedHere": False, + "owner": "Warpkeep Realm client", + "slice": "C", + "surface": ( + "content-addressed loading, markers, LOD allocation, instancing, picking, " + "mobile gestures, fallback, and accessibility" + ), + }, + { + "implementedHere": False, + "owner": "SpacetimeDB and authoritative Realm UI", + "slice": "D", + "surface": ( + "encounter state, combat, health, damage, actions, rewards, cooldowns, " + "and persistence" + ), + }, + { + "implementedHere": False, + "owner": "Warpkeep QA and operators", + "slice": "E", + "surface": "desktop and mobile budgets, migrations, observability, recovery, and rollout evidence", + }, + { + "implementedHere": False, + "owner": "Warpkeep owner", + "slice": "F", + "surface": "fail-closed activation and staged rollout", + }, +] +EXPECTED_BOUNDS = { + "LOD0_High": { + "min": [-0.7993891586294642, 0.0, -0.7494720541633232], + "max": [0.855703592300415, 2.14163864938768, 0.8585932850837708], + "size": [1.6550927509298794, 2.14163864938768, 1.6080653392470938], + }, + "LOD1_Balanced": { + "min": [-0.7993891586294642, 0.0, -0.7494720541633232], + "max": [0.7317647502528593, 2.142855711951041, 0.7091407179832458], + "size": [1.5311539088823234, 2.142855711951041, 1.458612772146569], + }, + "LOD2_Compact": { + "min": [-0.7993891586294642, 0.0, -0.6200000047683716], + "max": [0.7317647502528593, 2.1436644242985894, 0.7091407179832458], + "size": [1.5311539088823234, 2.1436644242985894, 1.3291407227516174], + }, + "LOD3_Map": { + "min": [-0.7870411055325282, 0.0, -0.6200000047683716], + "max": [0.6449754041834016, 2.1446314543004137, 0.7091407179832458], + "size": [1.4320165097159299, 2.1446314543004137, 1.3291407227516174], + }, +} +EXPECTED_ROOTS = { + "LOD0_High": "Warpkeep_CoreWatcher_Level1_LOD0_High", + "LOD1_Balanced": "Warpkeep_CoreWatcher_Level1_LOD1_Balanced", + "LOD2_Compact": "Warpkeep_CoreWatcher_Level1_LOD2_Compact", + "LOD3_Map": "Warpkeep_CoreWatcher_Level1_LOD3_Map", +} +EXPECTED_PART_NODES = { + "LOD0_High": [ + "CoreWatcher_BifurcatedBody_Left", "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", "CoreWatcher_CoreCage_2", + "CoreWatcher_CrownRib_Left", "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", "CoreWatcher_FloatingShard_2", + "CoreWatcher_FloatingShard_3", "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", "CoreWatcher_GroundFracture_4", + "CoreWatcher_GroundFracture_5", "CoreWatcher_GroundFracture_6", + "CoreWatcher_GroundFracture_7", "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", "CoreWatcher_GroundShard_3", + "CoreWatcher_GroundShard_4", "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore", + ], + "LOD1_Balanced": [ + "CoreWatcher_BifurcatedBody_Left", "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", "CoreWatcher_CoreCage_2", + "CoreWatcher_CrownRib_Left", "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", "CoreWatcher_FloatingShard_2", + "CoreWatcher_FloatingShard_3", "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", "CoreWatcher_GroundFracture_4", + "CoreWatcher_GroundFracture_5", "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", "CoreWatcher_GroundShard_3", + "CoreWatcher_LowerPedestal", "CoreWatcher_SuspendedCore", + ], + "LOD2_Compact": [ + "CoreWatcher_BifurcatedBody_Left", "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CoreCage_1", "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_2", "CoreWatcher_Footprint", + "CoreWatcher_GroundFracture_1", "CoreWatcher_GroundFracture_2", + "CoreWatcher_GroundFracture_3", "CoreWatcher_GroundShard_1", + "CoreWatcher_GroundShard_2", "CoreWatcher_LowerPedestal", + "CoreWatcher_SuspendedCore", + ], + "LOD3_Map": [ + "CoreWatcher_BifurcatedBody_Left", "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CrownRib_Left", "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", "CoreWatcher_FloatingShard_2", + "CoreWatcher_Footprint", "CoreWatcher_GroundFracture_1", + "CoreWatcher_GroundFracture_2", "CoreWatcher_GroundShard_1", + "CoreWatcher_LowerPedestal", "CoreWatcher_SuspendedCore", + ], +} + + +def _reject_constant(value: str) -> None: + raise ValueError(f"non-finite JSON number: {value}") + + +def _bounded_int(value: str) -> int: + if len(value) > MAX_JSON_NUMBER_CHARS: + raise ValueError("JSON integer token exceeds size limit") + return int(value) + + +def _bounded_float(value: str) -> float: + if len(value) > MAX_JSON_NUMBER_CHARS: + raise ValueError("JSON floating-point token exceeds size limit") + return float(value) + + +def _unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError("duplicate JSON key") + result[key] = value + return result + + +def _load_json(payload: bytes, label: str) -> dict: + if len(payload) > MAX_TRACKED_JSON_BYTES: + raise ValueError(f"JSON exceeds pre-parse size limit: {label}") + try: + document = json.loads( + payload.decode("utf-8"), + object_pairs_hook=_unique_object, + parse_constant=_reject_constant, + parse_float=_bounded_float, + parse_int=_bounded_int, + ) + except RecursionError as exc: + raise ValueError(f"JSON exceeds nesting limit: {label}") from exc + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise ValueError(f"invalid UTF-8 JSON: {label}") from exc + if not isinstance(document, dict): + raise ValueError(f"JSON root must be an object: {label}") + + pending: list[tuple[object, int]] = [(document, 1)] + while pending: + value, depth = pending.pop() + if depth > MAX_JSON_DEPTH: + raise ValueError(f"JSON exceeds nesting limit: {label}") + if isinstance(value, float) and not math.isfinite(value): + raise ValueError(f"non-finite JSON number: {label}") + if isinstance(value, dict): + pending.extend((child, depth + 1) for child in value.values()) + elif isinstance(value, list): + pending.extend((child, depth + 1) for child in value) + return document + + +def _strict_equal(actual: object, expected: object) -> bool: + if type(actual) is not type(expected): + return False + if isinstance(expected, dict): + return actual.keys() == expected.keys() and all( + _strict_equal(actual[key], value) for key, value in expected.items() + ) + if isinstance(expected, list): + return len(actual) == len(expected) and all( + _strict_equal(left, right) for left, right in zip(actual, expected) + ) + return actual == expected + + +def _expect(actual: object, expected: object, label: str) -> None: + if not _strict_equal(actual, expected): + raise ValueError(f"unexpected {label}") + + +def _exact_keys(value: object, keys: set[str], label: str) -> dict: + if not isinstance(value, dict) or set(value) != keys: + raise ValueError(f"unexpected field set in {label}") + return value + + +def _safe_relative(value: object) -> Path: + if ( + not isinstance(value, str) + or not value + or len(value) > MAX_TRACKED_PATH_CHARS + or "\\" in value + ): + raise ValueError("unsafe tracked path") + path = PurePosixPath(value) + if ( + path.is_absolute() + or PureWindowsPath(value).drive + or not value.isprintable() + or unicodedata.normalize("NFC", value) != value + or path.as_posix() != value + or len(path.parts) > MAX_TRACKED_PATH_COMPONENTS + or any(part in ("", ".", "..") for part in path.parts) + ): + raise ValueError("unsafe tracked path") + return Path(*path.parts) + + +def _regular_file_bytes( + root: Path, + relative: Path, + label: str, + *, + expected_bytes: int | None = None, + max_bytes: int, +) -> bytes: + if relative.is_absolute() or not relative.parts or any( + part in ("", ".", "..") for part in relative.parts + ): + raise ValueError(f"unsafe tracked path: {relative.as_posix()}") + + # Resolve the repository once, then walk every untrusted path component + # relative to open directory descriptors. Path.resolve()/lstat()/read_bytes() + # would leave a race in which an attacker could replace a checked path (or + # one of its parent directories) with a symlink before the subsequent open. + # O_NONBLOCK also prevents a raced-in FIFO or device from blocking before + # fstat can reject it. + if ( + os.open not in getattr(os, "supports_dir_fd", set()) + or not all( + hasattr(os, flag_name) + for flag_name in ("O_DIRECTORY", "O_NOFOLLOW", "O_NONBLOCK") + ) + ): + raise ValueError("platform cannot securely traverse tracked files") + directory_flags = os.O_RDONLY + file_flags = os.O_RDONLY + for flag_name in ("O_CLOEXEC", "O_NOFOLLOW"): + flag = getattr(os, flag_name, 0) + directory_flags |= flag + file_flags |= flag + directory_flags |= os.O_DIRECTORY + file_flags |= os.O_NONBLOCK + + descriptors: list[int] = [] + try: + directory_fd = os.open(root, directory_flags) + descriptors.append(directory_fd) + if not stat.S_ISDIR(os.fstat(directory_fd).st_mode): + raise ValueError("repository root must be a directory") + + for component in relative.parts[:-1]: + directory_fd = os.open( + component, + directory_flags, + dir_fd=directory_fd, + ) + descriptors.append(directory_fd) + if not stat.S_ISDIR(os.fstat(directory_fd).st_mode): + raise ValueError( + f"tracked path contains a non-directory: {relative.as_posix()}" + ) + + file_fd = os.open( + relative.parts[-1], + file_flags, + dir_fd=directory_fd, + ) + descriptors.append(file_fd) + before = os.fstat(file_fd) + if not stat.S_ISREG(before.st_mode): + raise ValueError( + "tracked path must end in a regular non-symlink file: " + f"{relative.as_posix()}" + ) + if before.st_size > max_bytes: + raise ValueError(f"tracked file exceeds pre-read size limit: {label}") + if expected_bytes is not None and before.st_size != expected_bytes: + raise ValueError(f"tracked byte count mismatch: {label}") + + chunks: list[bytes] = [] + remaining = before.st_size + 1 + while remaining: + chunk = os.read(file_fd, min(64 * 1024, remaining)) + if not chunk: + break + chunks.append(chunk) + remaining -= len(chunk) + payload = b"".join(chunks) + after = os.fstat(file_fd) + stable_fields = ("st_dev", "st_ino", "st_size", "st_mtime_ns", "st_ctime_ns") + if ( + len(payload) != before.st_size + or any(getattr(before, field) != getattr(after, field) for field in stable_fields) + ): + raise ValueError(f"tracked file changed while reading: {label}") + return payload + except OSError as exc: + raise ValueError( + "tracked path must end in a regular non-symlink file and contain " + f"no symlink directories: {relative.as_posix()}" + ) from exc + finally: + for descriptor in reversed(descriptors): + try: + os.close(descriptor) + except OSError: + pass + + +def _tracked_bytes(root: Path, record: dict, label: str) -> bytes: + _exact_keys(record, {"bytes", "sha256", "trackedPath"}, label) + declared_bytes = record["bytes"] + if type(declared_bytes) is not int or not 0 < declared_bytes <= MAX_TRACKED_JSON_BYTES: + raise ValueError(f"invalid tracked byte count: {label}") + relative = _safe_relative(record["trackedPath"]) + payload = _regular_file_bytes( + root, + relative, + label, + expected_bytes=declared_bytes, + max_bytes=MAX_TRACKED_JSON_BYTES, + ) + digest = record["sha256"] + if not isinstance(digest, str) or not SHA256_RE.fullmatch(digest): + raise ValueError(f"invalid tracked SHA-256: {label}") + if hashlib.sha256(payload).hexdigest() != digest: + raise ValueError(f"tracked SHA-256 mismatch: {label}") + return payload + + +def _package_verifier(): + # --root is untrusted verification data. Never import Python from it. + path = Path(__file__).resolve().with_name("verify_core_watcher_level1.py") + spec = importlib.util.spec_from_file_location("_core_watcher_package_verifier", path) + if spec is None or spec.loader is None: + raise ValueError("unable to load Core Watcher package verifier") + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def _skip_json_whitespace(raw: bytes, offset: int, limit: int) -> int: + while offset < limit and raw[offset] in b" \t\r\n": + offset += 1 + return offset + + +def _json_string_end(raw: bytes, offset: int, limit: int) -> int: + if offset >= limit or raw[offset] != ord('"'): + raise ValueError("contract digest locator expected a JSON string") + offset += 1 + while offset < limit: + byte = raw[offset] + if byte == ord('"'): + return offset + 1 + if byte == ord("\\"): + offset += 2 + else: + offset += 1 + raise ValueError("unterminated JSON string while locating contract digest") + + +def _json_value_end(raw: bytes, offset: int, limit: int) -> int: + offset = _skip_json_whitespace(raw, offset, limit) + if offset >= limit: + raise ValueError("missing JSON value while locating contract digest") + if raw[offset] == ord('"'): + return _json_string_end(raw, offset, limit) + if raw[offset] not in (ord("{"), ord("[")): + end = offset + while end < limit and raw[end] not in b" \t\r\n,]}": + end += 1 + if end == offset: + raise ValueError("missing scalar JSON value while locating contract digest") + return end + + closing_for = {ord("{"): ord("}"), ord("["): ord("]")} + stack = [closing_for[raw[offset]]] + offset += 1 + while offset < limit and stack: + byte = raw[offset] + if byte == ord('"'): + offset = _json_string_end(raw, offset, limit) + continue + if byte in closing_for: + stack.append(closing_for[byte]) + elif byte in (ord("}"), ord("]")): + if byte != stack.pop(): + raise ValueError("mismatched JSON container while locating contract digest") + offset += 1 + if stack: + raise ValueError("unterminated JSON container while locating contract digest") + return offset + + +def _json_object_field_spans( + raw: bytes, + start: int, + end: int, +) -> dict[str, tuple[int, int]]: + offset = _skip_json_whitespace(raw, start, end) + if offset >= end or raw[offset] != ord("{"): + raise ValueError("contract digest locator expected a JSON object") + offset += 1 + fields: dict[str, tuple[int, int]] = {} + while True: + offset = _skip_json_whitespace(raw, offset, end) + if offset < end and raw[offset] == ord("}"): + return fields + key_start = offset + key_end = _json_string_end(raw, key_start, end) + try: + key = json.loads(raw[key_start:key_end].decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise ValueError("invalid JSON key while locating contract digest") from exc + if not isinstance(key, str) or key in fields: + raise ValueError("invalid or duplicate JSON key while locating contract digest") + offset = _skip_json_whitespace(raw, key_end, end) + if offset >= end or raw[offset] != ord(":"): + raise ValueError("missing JSON member separator while locating contract digest") + value_start = _skip_json_whitespace(raw, offset + 1, end) + value_end = _json_value_end(raw, value_start, end) + fields[key] = (value_start, value_end) + offset = _skip_json_whitespace(raw, value_end, end) + if offset >= end: + raise ValueError("unterminated JSON object while locating contract digest") + if raw[offset] == ord(","): + offset += 1 + continue + if raw[offset] == ord("}"): + return fields + raise ValueError("invalid JSON object separator while locating contract digest") + + +def _zeroed_contract_digest_bytes(raw: bytes, declared: str) -> bytes: + document_start = _skip_json_whitespace(raw, 0, len(raw)) + document_end = _json_value_end(raw, document_start, len(raw)) + if _skip_json_whitespace(raw, document_end, len(raw)) != len(raw): + raise ValueError("trailing bytes while locating contract digest") + root_fields = _json_object_field_spans(raw, document_start, document_end) + try: + contract_start, contract_end = root_fields["contractDigest"] + except KeyError as exc: + raise ValueError("contract digest object is missing") from exc + contract_fields = _json_object_field_spans(raw, contract_start, contract_end) + try: + value_start, value_end = contract_fields["sha256"] + except KeyError as exc: + raise ValueError("contract digest SHA-256 field is missing") from exc + token = declared.encode("ascii") + if raw[value_start:value_end] != b'"' + token + b'"': + raise ValueError( + "contract digest must be 64 literal lowercase hexadecimal characters at " + "$.contractDigest.sha256" + ) + return raw[: value_start + 1] + ZERO_DIGEST.encode("ascii") + raw[value_end - 1 :] + + +def _verify_digest(raw: bytes, profile: dict) -> str: + digest = _exact_keys( + profile.get("contractDigest"), + {"algorithm", "canonicalization", "sha256"}, + "contractDigest", + ) + _expect(digest["algorithm"], "sha256", "contract digest algorithm") + _expect(digest["canonicalization"], CANONICALIZATION, "canonicalization") + declared = digest["sha256"] + if not isinstance(declared, str) or not SHA256_RE.fullmatch(declared): + raise ValueError("invalid contract digest") + zeroed = _zeroed_contract_digest_bytes(raw, declared) + actual = hashlib.sha256(zeroed).hexdigest() + if actual != declared: + raise ValueError("contract digest mismatch") + if declared != EXPECTED_PROFILE_DIGEST: + raise ValueError("integration profile digest is not the production-pinned digest") + return declared + + +def _verify_bindings(root: Path, profile: dict) -> tuple[dict, dict]: + binding = _exact_keys( + profile.get("assetBinding"), + { + "archive", "gallery", "packageRoot", "releaseManifest", + "runtimeManifest", "sourceManifest", "sourceSemanticFingerprintSha256", + }, + "assetBinding", + ) + archive = _exact_keys(binding["archive"], {"bytes", "entries", "name", "sha256"}, "archive binding") + _expect( + archive, + { + "bytes": 1405757, + "entries": 15, + "name": "warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip", + "sha256": "34c8a80186642659acea893c06199a8e7b615ac0f9685f2c58c4a27641f56a33", + }, + "archive binding", + ) + _expect(binding["packageRoot"], "Warpkeep_CoreWatcher_Level1_GameReady", "package root") + _expect( + binding["sourceSemanticFingerprintSha256"], + "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c", + "source semantic fingerprint", + ) + + _expect(binding["releaseManifest"], EXPECTED_RELEASE_BINDING, "release manifest binding") + _expect(binding["gallery"], EXPECTED_GALLERY_BINDING, "gallery binding") + _expect(binding["runtimeManifest"], EXPECTED_RUNTIME_BINDING, "runtime manifest binding") + _expect(binding["sourceManifest"], EXPECTED_SOURCE_BINDING, "source manifest binding") + + release_payload = _tracked_bytes(root, binding["releaseManifest"], "release manifest binding") + sidecar_payload = _regular_file_bytes( + root, + CHECKSUM_SIDECAR_PATH, + "release checksum sidecar", + expected_bytes=EXPECTED_CHECKSUM_SIDECAR_BYTES, + max_bytes=MAX_TRACKED_JSON_BYTES, + ) + if hashlib.sha256(sidecar_payload).hexdigest() != EXPECTED_CHECKSUM_SIDECAR_SHA256: + raise ValueError("release checksum sidecar SHA-256 mismatch") + expected_sidecar = f"{archive['sha256']} {archive['name']}\n".encode("ascii") + if sidecar_payload != expected_sidecar: + raise ValueError("release checksum sidecar does not match archive binding") + gallery_payload = _tracked_bytes(root, binding["gallery"], "gallery binding") + source_payload = _tracked_bytes(root, binding["sourceManifest"], "source manifest binding") + release = _load_json(release_payload, RELEASE_MANIFEST_PATH.as_posix()) + gallery = _load_json(gallery_payload, GALLERY_PATH.as_posix()) + source = _load_json(source_payload, EXPECTED_SOURCE_BINDING["trackedPath"]) + sanitization = source.get("sanitization") + if not isinstance(sanitization, dict): + raise ValueError("source manifest sanitization record is missing") + _expect( + sanitization.get("sourceSemanticFingerprintSha256"), + binding["sourceSemanticFingerprintSha256"], + "source-manifest semantic fingerprint", + ) + + runtime_binding = _exact_keys( + binding["runtimeManifest"], + {"bytes", "packagePath", "sha256", "trackedPath"}, + "runtime manifest binding", + ) + _expect(runtime_binding["trackedPath"], RUNTIME_PATH.as_posix(), "runtime tracked path") + runtime_payload = _regular_file_bytes( + root, + _safe_relative(runtime_binding["trackedPath"]), + "runtime manifest", + expected_bytes=runtime_binding["bytes"], + max_bytes=MAX_TRACKED_JSON_BYTES, + ) + if len(runtime_payload) != runtime_binding["bytes"]: + raise ValueError("runtime manifest byte count mismatch") + if hashlib.sha256(runtime_payload).hexdigest() != runtime_binding["sha256"]: + raise ValueError("runtime manifest SHA-256 mismatch") + + attachments = release.get("attachments") + if not isinstance(attachments, list) or len(attachments) != 1: + raise ValueError("release manifest must contain one attachment") + attachment = attachments[0] + if not isinstance(attachment, dict): + raise ValueError("release attachment record must be an object") + for key in ("bytes", "name", "sha256", "packageRoot"): + _expect(attachment.get(key), archive[key] if key != "packageRoot" else binding["packageRoot"], f"release {key}") + entries = attachment.get("entries") + if not isinstance(entries, list) or len(entries) != archive["entries"]: + raise ValueError("release entry count mismatch") + if any(not isinstance(item, dict) for item in entries): + raise ValueError("release entries must be objects") + entry = next((item for item in entries if item.get("path") == runtime_binding["packagePath"]), None) + if not isinstance(entry, dict): + raise ValueError("runtime manifest release entry missing") + _expect(entry.get("bytes"), runtime_binding["bytes"], "runtime release bytes") + _expect(entry.get("sha256"), runtime_binding["sha256"], "runtime release SHA-256") + return _load_json(runtime_payload, RUNTIME_PATH.as_posix()), {"release": release, "gallery": gallery, "entries": entries} + + +def _verify_profiles(profile: dict, runtime: dict) -> list[dict]: + records = profile.get("profiles") + lods = runtime.get("lods") + if not isinstance(records, list) or len(records) != 4 or not isinstance(lods, list) or len(lods) != 4: + raise ValueError("exactly four integration profiles and runtime LODs are required") + profile_keys = { + "boundsGltfMeters", "bytes", "drawCalls", "embeddedBufferBytes", "file", + "id", "materials", "meshes", "nodes", "onePrimitivePerMesh", "partNodes", + "primitives", "rootNode", "sha256", "tier", "triangles", "uploadedVertices", + } + ids = ["high", "balanced", "compact", "map"] + metric_keys = { + "bytes", "embeddedBufferBytes", "file", "materials", "meshes", "nodes", + "primitives", "sha256", "tier", "triangles", "uploadedVertices", + } + for record, lod, expected_id in zip(records, lods, ids): + _exact_keys(record, profile_keys, f"profile {expected_id}") + if not isinstance(lod, dict): + raise ValueError(f"runtime LOD {expected_id} must be an object") + _expect(record["id"], expected_id, f"profile id {expected_id}") + for key in metric_keys: + _expect(record[key], lod.get(key), f"{record['tier']} {key}") + tier = record["tier"] + _expect(record["boundsGltfMeters"], EXPECTED_BOUNDS[tier], f"{tier} emitted bounds") + _expect(record["rootNode"], EXPECTED_ROOTS[tier], f"{tier} root node") + _expect(record["partNodes"], EXPECTED_PART_NODES[tier], f"{tier} part nodes") + if record["onePrimitivePerMesh"] is not True or not ( + record["drawCalls"] == record["primitives"] == record["meshes"] == len(record["partNodes"]) + ): + raise ValueError(f"{tier} draw/mesh/semantic-part contract mismatch") + return records + + +def _verify_previews(root: Path, profile: dict, evidence: dict) -> None: + presentation = _exact_keys( + profile.get("presentation"), + {"description", "levelLabel", "packageReviewArt", "previews", "shortLabel", "uiSemanticsComeFromEngine"}, + "presentation", + ) + _expect( + { + "description": presentation["description"], + "levelLabel": presentation["levelLabel"], + "shortLabel": presentation["shortLabel"], + "uiSemanticsComeFromEngine": presentation["uiSemanticsComeFromEngine"], + }, + { + "description": ( + "A dormant Core Watcher presentation. Realm state and actions " + "remain server-authoritative." + ), + "levelLabel": "Level 1", + "shortLabel": "Watcher", + "uiSemanticsComeFromEngine": True, + }, + "presentation semantics", + ) + if presentation["uiSemanticsComeFromEngine"] is not True: + raise ValueError("presentation semantics must come from the engine") + previews = presentation["previews"] + if not isinstance(previews, list) or len(previews) != 3: + raise ValueError("exactly three tracked previews are required") + verifier = _package_verifier() + gallery_records = evidence["gallery"].get("images") + if not isinstance(gallery_records, list) or len(gallery_records) != 3: + raise ValueError("gallery must contain exactly three images") + for record, gallery_record in zip(previews, gallery_records): + _exact_keys(record, {"bytes", "decorative", "height", "path", "runtimeUse", "sha256", "width"}, "preview") + if not isinstance(gallery_record, dict): + raise ValueError("gallery preview record must be an object") + if record["runtimeUse"] is not False or record["decorative"] is not True: + raise ValueError("review previews must remain decorative and runtime-disabled") + preview_bytes = record["bytes"] + if type(preview_bytes) is not int or not 0 < preview_bytes <= MAX_PREVIEW_BYTES: + raise ValueError("invalid preview byte count") + payload = _regular_file_bytes( + root, + _safe_relative(record["path"]), + "preview", + expected_bytes=preview_bytes, + max_bytes=MAX_PREVIEW_BYTES, + ) + if len(payload) != record["bytes"] or hashlib.sha256(payload).hexdigest() != record["sha256"]: + raise ValueError("preview bytes or SHA-256 mismatch") + dimensions = ( + verifier._png_dimensions(payload, record["path"]) + if record["path"].endswith(".png") + else verifier._jpeg_dimensions(payload, record["path"]) + ) + _expect(list(dimensions), [record["width"], record["height"]], "preview dimensions") + _expect(gallery_record.get("bytes"), record["bytes"], "gallery preview bytes") + _expect(gallery_record.get("sha256"), record["sha256"], "gallery preview SHA-256") + + art = _exact_keys( + presentation["packageReviewArt"], + {"bytes", "decorative", "height", "packagePath", "runtimeUse", "sha256", "width"}, + "package review art", + ) + _expect( + art, + { + "bytes": 580483, + "decorative": True, + "height": 1600, + "packagePath": ( + "Warpkeep_CoreWatcher_Level1_GameReady/Previews/" + "Warpkeep_CoreWatcher_Level1_Transparent_1600.png" + ), + "runtimeUse": False, + "sha256": "bc20fa28239d8008b79f182509363a81b2bef6705fdf8436786ca70567e2cf9a", + "width": 1600, + }, + "package review art", + ) + if art["runtimeUse"] is not False or art["decorative"] is not True: + raise ValueError("package review art must remain decorative and runtime-disabled") + entry = next((item for item in evidence["entries"] if item.get("path") == art["packagePath"]), None) + if not isinstance(entry, dict) or entry.get("bytes") != art["bytes"] or entry.get("sha256") != art["sha256"]: + raise ValueError("package review art release binding mismatch") + + +def _verify_policies(profile: dict, records: list[dict]) -> None: + _expect(profile.get("status"), EXPECTED_STATUS, "status gates") + _expect(profile.get("authorityBoundary"), EXPECTED_AUTHORITY, "authority boundary") + _expect(profile.get("loading"), EXPECTED_LOADING, "loading policy") + _expect(profile.get("qualityCamera"), EXPECTED_QUALITY, "quality/camera policy") + _expect(profile.get("selectionAndGestures"), EXPECTED_SELECTION, "selection and gesture policy") + + geometry = _exact_keys( + profile.get("geometry"), + {"frontAxis", "nativeScaleMetersPerUnit", "pivot", "renderGeometryIsCollision", "renderGeometryIsPicking", "selectionHint", "units", "upAxis"}, + "geometry", + ) + _expect( + geometry, + { + "frontAxis": "+Z", "nativeScaleMetersPerUnit": 1.0, + "pivot": "footprint-center-ground", "renderGeometryIsCollision": False, + "renderGeometryIsPicking": False, + "selectionHint": { + "centerYMeters": 1.275, "engineOwned": True, "heightMeters": 2.55, + "presentationFootprintRadiusMeters": 0.9, + "packageSuggestedRadiusMeters": 0.72, "shape": "cylinder", + }, + "units": "meters", "upAxis": "+Y", + }, + "geometry contract", + ) + identity = profile.get("identity") + _expect( + identity, + { + "accessibleName": "Level 1 Core Watcher", + "assetId": "warpkeep.encounters.core.watcher.level1", + "combatEnabled": False, "displayName": "Core Watcher", "encounterLevel": 1, + "enemyKind": "core-watcher", "faction": "The Core", + "revision": "genesis-001-core-watcher-level1-2026-08-03", + "statePresentation": "dormant-presence", + }, + "identity", + ) + + instancing = _exact_keys( + profile.get("instancing"), + {"capacityEvidence", "eligible", "gameplayStatePerRenderInstance", "onePrimitivePerMesh", "selectedHighDetailMaxInstances", "staticRigid", "strategy"}, + "instancing", + ) + for key, expected in ( + ("eligible", True), ("gameplayStatePerRenderInstance", False), + ("onePrimitivePerMesh", True), ("selectedHighDetailMaxInstances", 1), + ("staticRigid", True), ("strategy", "per-semantic-mesh"), + ): + _expect(instancing[key], expected, f"instancing {key}") + capacity = _exact_keys( + instancing["capacityEvidence"], + {"declaresPopulation", "mapProfileInstancedDrawGroups", "mapProfileNaiveCloneDrawCalls", "mapProfileVisibleTriangles", "nonAuthoritativeTestInstances"}, + "capacity evidence", + ) + count = capacity["nonAuthoritativeTestInstances"] + map_profile = records[-1] + if type(count) is not int or count != 72 or capacity["declaresPopulation"] is not False: + raise ValueError("capacity evidence must remain a non-authoritative 72-instance test") + if ( + capacity["mapProfileVisibleTriangles"] != count * map_profile["triangles"] + or capacity["mapProfileNaiveCloneDrawCalls"] != count * map_profile["drawCalls"] + or capacity["mapProfileInstancedDrawGroups"] != map_profile["drawCalls"] + ): + raise ValueError("capacity evidence math mismatch") + + motion = profile.get("motion") + _expect(motion, EXPECTED_MOTION, "motion policy") + targets = motion["targets"] + if not isinstance(targets, list) or len(targets) != 3: + raise ValueError("motion targets mismatch") + target_names = [target.get("node") for target in targets if isinstance(target, dict)] + if len(target_names) != 3 or any( + name not in record["partNodes"] for name in target_names for record in records + ): + raise ValueError("motion target is not stable across every LOD") + + _expect( + profile.get("fallbackAndAccessibility"), + EXPECTED_FALLBACK, + "fallback/accessibility policy", + ) + _expect( + profile.get("telemetryAndPrivacy"), + EXPECTED_TELEMETRY, + "telemetry/privacy boundary", + ) + _expect( + profile.get("futureGameplaySlices"), + EXPECTED_FUTURE_SLICES, + "future gameplay slices", + ) + + +def verify(root: Path | str) -> str: + try: + repository = Path(root).resolve(strict=True) + except (OSError, RuntimeError) as exc: + raise ValueError("repository root cannot be resolved safely") from exc + raw = _regular_file_bytes( + repository, + PROFILE_PATH, + "integration profile", + max_bytes=MAX_PROFILE_BYTES, + ) + profile = _load_json(raw, PROFILE_PATH.as_posix()) + _exact_keys(profile, ROOT_KEYS, "integration profile") + _expect(profile["schema"], "warpkeep.asset-integration-profile.v1", "profile schema") + _expect(profile["version"], "1.0.0", "profile version") + digest = _verify_digest(raw, profile) + runtime, evidence = _verify_bindings(repository, profile) + records = _verify_profiles(profile, runtime) + _verify_previews(repository, profile, evidence) + _verify_policies(profile, records) + + forbidden = (b"/Users/", b"/home/", b"/var/folders/", b"/tmp/") + if any(token in raw for token in forbidden): + raise ValueError("private path found in integration profile") + return digest + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Verify the tracked Core Watcher review-only integration profile." + ) + parser.add_argument( + "--root", + type=Path, + default=Path(__file__).resolve().parents[1], + help="Warpkeep-Assets repository root (defaults to this checkout)", + ) + args = parser.parse_args() + try: + digest = verify(args.root) + except (OSError, ValueError) as exc: + parser.exit(1, f"Core Watcher integration-profile verification failed: {exc}\n") + print( + "Verified review-only Core Watcher integration profile " + f"{digest}; no runtime, gameplay, release, or activation authority." + ) + + +if __name__ == "__main__": + main() diff --git a/scripts/verify_core_watcher_level1.py b/scripts/verify_core_watcher_level1.py new file mode 100755 index 0000000..60bcbb8 --- /dev/null +++ b/scripts/verify_core_watcher_level1.py @@ -0,0 +1,2762 @@ +#!/usr/bin/env python3 +"""Structurally verify the Core Watcher Level 1 release-candidate package. + +This dependency-free verifier accepts either a ZIP candidate or its extracted +package root and checks the declared Core Watcher structure, runtime contract, +GLB geometry, and nested checksums. It is not an authenticity trust anchor. +Authenticate an exact release archive separately with the tracked +``releases/core-watcher-level1-2026-08-03/manifest.json`` and adjacent +``SHA256SUMS.txt`` by using ``scripts/verify_release.py``. +""" + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +import hashlib +import json +import math +import os +from pathlib import Path, PurePosixPath, PureWindowsPath +import re +import stat +import struct +import unicodedata +import zlib +from zipfile import BadZipFile, ZipFile, ZipInfo, ZIP_DEFLATED, ZIP_STORED + + +PACKAGE_NAME = "Warpkeep_CoreWatcher_Level1_GameReady" +RUNTIME_DIRECTORY = "Runtime/Encounters/Core/WatcherLevel1" +RUNTIME_MANIFEST = f"{RUNTIME_DIRECTORY}/runtime-manifest.json" +SOURCE_BLEND = "Source/Warpkeep_CoreWatcher_Level1_Editable.blend" +QA_REPORT = "QA/Warpkeep_CoreWatcher_Level1_RuntimeQA.json" +ASSET_MANIFEST = "asset-manifest.json" +CHECKSUMS = "SHA256SUMS.txt" +REVISION = "genesis-001-core-watcher-level1-2026-08-03" +ASSET_ID = "warpkeep.encounters.core.watcher.level1" +SOURCE_SEMANTIC_FINGERPRINT_SHA256 = ( + "a51eae5665ee3e7c59191b36dd1abfbbc1fa3ddd76405bee52c6c5fb3dad344c" +) +INTEGRATION_PROFILE_PATH = ( + Path(__file__).resolve().parents[1] + / "contracts/core-watcher-level1-2026-08-03.integration-profile.json" +) +INTEGRATION_PROFILE_SHA256 = ( + "0a34614dfb42f754fd2524b23ef213c2db502768ad9230bd6a27a9198a8251c0" +) + +RUNTIME_LOD_GUIDANCE = { + "LOD0_High": "selected inspection and close Realm zoom", + "LOD1_Balanced": "nearby normal-quality Realm view", + "LOD2_Compact": "medium distance and reduced-quality selected view", + "LOD3_Map": "far map signal and static reduced-motion presentation", + "suggestedDistancesMeters": { + "LOD0_HighThrough": 8, + "LOD1_BalancedThrough": 18, + "LOD2_CompactThrough": 36, + "LOD3_MapThrough": 72, + }, +} +RUNTIME_MOTION_CONTRACT = { + "animations": [], + "continuousMotionRequired": False, + "forbiddenClips": ["Attack", "Walk", "Death"], + "mode": "bounded-runtime-rigid-hierarchy", + "reducedMotion": "static", + "skins": 0, +} +RUNTIME_SELECTION_GUIDANCE = { + "presentationFootprintRadiusMeters": 0.9, + "renderGeometryIsAuthoritativeCollision": False, + "suggestedPickCylinderHeightMeters": 2.55, + "suggestedPickCylinderRadiusMeters": 0.72, +} +RUNTIME_DESIGN_INTENT = { + "camera": "three-quarter isometric 4X world map and selected encounter record", + "excluded": ( + "human face, legs, weapon, gun, wings, banner, heraldry, spaceship, " + "modern robot" + ), + "identity": ( + "ancient fantasy-machine infrastructure expressed through obsidian and cold " + "ultraviolet" + ), + "silhouette": "tall bifurcated monolith, suspended core, asymmetric floating shards", +} +RUNTIME_AUTHORING_CONTRACT = { + "animations": [], + "front": "+Z glTF / -Y Blender", + "lods": ["LOD0_High", "LOD1_Balanced", "LOD2_Compact", "LOD3_Map"], + "metersPerUnit": 1.0, + "motion": "optional bounded runtime rigid hierarchy; static under reduced motion", + "selfContained": True, + "textures": 0, +} + +LOD_CONTRACT = ( + ( + "LOD0_High", + "Warpkeep_CoreWatcher_Level1_LOD0_High_Runtime.glb", + 3500, + 180224, + ), + ( + "LOD1_Balanced", + "Warpkeep_CoreWatcher_Level1_LOD1_Balanced_Runtime.glb", + 2200, + 102400, + ), + ( + "LOD2_Compact", + "Warpkeep_CoreWatcher_Level1_LOD2_Compact_Runtime.glb", + 1100, + 61440, + ), + ( + "LOD3_Map", + "Warpkeep_CoreWatcher_Level1_LOD3_Map_Runtime.glb", + 600, + 35840, + ), +) + +PREVIEW_DIMENSIONS = { + "Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg": (2400, 1200), + "Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg": (1920, 1080), + "Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png": (1600, 1600), + "Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png": (512, 512), +} + +EXPECTED_FILES = frozenset( + { + "PACKAGE-NOTICE.md", + "README.md", + CHECKSUMS, + ASSET_MANIFEST, + SOURCE_BLEND, + QA_REPORT, + RUNTIME_MANIFEST, + *PREVIEW_DIMENSIONS, + *(f"{RUNTIME_DIRECTORY}/{filename}" for _, filename, _, _ in LOD_CONTRACT), + } +) + +EXPECTED_MATERIALS = frozenset( + { + "WK_Core_Obsidian", + "WK_Core_BlackenedMetal", + "WK_Core_Ultraviolet", + } +) +ALLOWED_EXTENSIONS = frozenset({"KHR_materials_emissive_strength"}) +AUTHORITY_BOUNDARY = { + "ai": False, + "collision": False, + "combat": False, + "damage": False, + "health": False, + "ownership": False, + "picking": False, + "placement": False, + "respawn": False, + "rewards": False, + "routing": False, + "spacetimeDb": False, + "visualOnly": True, +} + +LOD_QA_CHECK_SUFFIXES = ( + "GLB 2.0 header and chunk integrity", + "triangle ceiling", + "byte ceiling", + "one scene", + "finite bounded geometry", + "self-contained embedded buffer", + "texture-free opaque runtime", + "static rigid runtime", + "no cameras or unsupported extensions", + "ground contact", + "map footprint bound", + "stable height", +) +EXPECTED_QA_CHECKS = ( + "exact four runtime LODs", + "strict triangle reduction", + "strict byte reduction", + *( + f"{tier} {suffix}" + for tier, _, _, _ in LOD_CONTRACT + for suffix in LOD_QA_CHECK_SUFFIXES + ), + "one closed enemy kind", + "exact Level 1 classification", + "combat disabled", + "zero gameplay authority", + "no Hegemony heraldry or textures", + "source semantic fingerprint pinned", + "required previews written", +) +if len(EXPECTED_QA_CHECKS) != 58 or len(set(EXPECTED_QA_CHECKS)) != 58: + raise RuntimeError("internal Core Watcher QA check contract is invalid") + +FORBIDDEN_PNG_CHUNKS = frozenset({b"eXIf", b"tEXt", b"zTXt", b"iTXt", b"tIME"}) +FORBIDDEN_JPEG_MARKERS = { + 0xFE: "COM", + 0xE1: "APP1", + 0xE2: "APP2", + 0xED: "APP13", +} + +MEBIBYTE = 1024 * 1024 +MAX_ARCHIVE_BYTES = 32 * MEBIBYTE +MAX_TOTAL_BYTES = 48 * MEBIBYTE +MAX_ENTRY_BYTES = 16 * MEBIBYTE +MAX_TEXT_BYTES = 2 * MEBIBYTE +MAX_COMPRESSION_RATIO = 200 +MAX_ARCHIVE_ENTRIES = 64 +MAX_JSON_BYTES = 256 * 1024 +MAX_JSON_DEPTH = 64 +MAX_JSON_VALUES = 50_000 +MAX_JSON_NUMBER_CHARS = 128 +MAX_GLB_NODES = 64 +MAX_GLB_MESHES = 64 +MAX_GLB_BUFFER_VIEWS = 256 +MAX_GLB_ACCESSORS = 256 +MAX_ACCESSOR_ELEMENTS = 20_000 +MAX_DECODED_ACCESSOR_ELEMENTS = 100_000 +MAX_PNG_DECOMPRESSED_BYTES = 32 * MEBIBYTE +MAX_AUTHORING_BOUND_MARGIN_METERS = 0.08 +BOUND_TOLERANCE_METERS = 1e-5 +NORMAL_LENGTH_TOLERANCE = 1e-4 +QUATERNION_LENGTH_TOLERANCE = 1e-5 +SHA256_RE = re.compile(r"[0-9a-f]{64}") +CHECKSUM_LINE_RE = re.compile(r"([0-9a-f]{64}) ([^\r\n]+)") + +PRIVATE_PATTERNS = ( + ("macOS private home path", re.compile(rb"/Users/", re.IGNORECASE)), + ("Unix private home path", re.compile(rb"/home/", re.IGNORECASE)), + ( + "macOS temporary build path", + re.compile(rb"/(?:private/)?var/folders/", re.IGNORECASE), + ), + ("Unix temporary build path", re.compile(rb"/(?:var/)?tmp/", re.IGNORECASE)), + ("Windows private home path", re.compile(rb"[A-Za-z]:\\\\Users\\\\", re.IGNORECASE)), + ( + "Windows temporary build path", + re.compile(rb"\\\\AppData\\\\Local\\\\Temp\\\\", re.IGNORECASE), + ), +) +CREDENTIAL_PATTERNS = ( + ("GitHub credential", re.compile(rb"github_pat_[A-Za-z0-9_]{10,}")), + ("GitHub credential", re.compile(rb"gh[pousr]_[A-Za-z0-9]{20,}")), + ("OpenAI credential", re.compile(rb"sk-(?:proj-)?[A-Za-z0-9_-]{16,}")), + ("AWS credential", re.compile(rb"(?:AKIA|ASIA)[A-Z0-9]{16}")), + ("Slack credential", re.compile(rb"xox[baprs]-[A-Za-z0-9-]{10,}")), + ( + "private key", + re.compile(rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"), + ), +) + +COMPONENTS = { + 5120: (1, "b"), + 5121: (1, "B"), + 5122: (2, "h"), + 5123: (2, "H"), + 5125: (4, "I"), + 5126: (4, "f"), +} +TYPE_COMPONENTS = {"SCALAR": 1, "VEC2": 2, "VEC3": 3, "VEC4": 4} + +EXPECTED_DIRECTORIES = frozenset( + parent.as_posix() + for name in EXPECTED_FILES + for parent in PurePosixPath(name).parents + if parent != PurePosixPath(".") +) +QA_GENERATED_AT = "2026-08-03T12:00:00+00:00" + + +@dataclass(frozen=True) +class RuntimeMaterial: + name: str + alpha_mode: str + opaque: bool + double_sided: bool + base_color_factor: tuple[float, float, float, float] + metallic: float + roughness: float + emissive_factor: tuple[float, float, float] + emissive_strength: float + + +@dataclass(frozen=True) +class GlbSemanticContract: + tier: str + profile_id: str + root_node: str + part_nodes: tuple[str, ...] + + +@dataclass(frozen=True) +class GlbSemanticEvidence: + root_node: str + part_nodes: tuple[str, ...] + semantic_roles: tuple[tuple[str, str], ...] + material_assignments: tuple[tuple[str, str], ...] + + +@dataclass(frozen=True) +class GlbMetrics: + bytes: int + sha256: str + triangles: int + uploaded_vertices: int + embedded_buffer_bytes: int + scenes: int + nodes: int + meshes: int + primitives: int + materials: int + images: int + textures: int + samplers: int + cameras: int + skins: int + animations: int + extensions_used: tuple[str, ...] + runtime_materials: tuple[RuntimeMaterial, ...] + bounds_gltf_min: tuple[float, float, float] + bounds_gltf_max: tuple[float, float, float] + bounds_gltf_size: tuple[float, float, float] + footprint_radius: float + semantic: GlbSemanticEvidence + + +@dataclass(frozen=True) +class VerificationResult: + source: Path + files: int + lods: int + triangles: tuple[int, ...] + bytes: tuple[int, ...] + + +def _reject_json_constant(value: str) -> None: + raise ValueError(f"non-finite JSON number: {value}") + + +def _parse_json_int(value: str) -> int: + if len(value) > MAX_JSON_NUMBER_CHARS: + raise ValueError("JSON integer token exceeds size limit") + return int(value) + + +def _parse_json_float(value: str) -> float: + if len(value) > MAX_JSON_NUMBER_CHARS: + raise ValueError("JSON number token exceeds size limit") + number = float(value) + if not math.isfinite(number): + raise ValueError(f"non-finite JSON number: {value}") + return number + + +def _unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError(f"duplicate JSON key: {key!r}") + result[key] = value + return result + + +def load_json(payload: bytes, label: str) -> dict: + if len(payload) > MAX_JSON_BYTES: + raise ValueError(f"JSON file exceeds size limit: {label}") + try: + document = json.loads( + payload.decode("utf-8"), + object_pairs_hook=_unique_object, + parse_constant=_reject_json_constant, + parse_float=_parse_json_float, + parse_int=_parse_json_int, + ) + except RecursionError as exc: + raise ValueError(f"JSON nesting exceeds depth limit: {label}") from exc + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise ValueError(f"invalid UTF-8 JSON: {label}") from exc + if not isinstance(document, dict): + raise ValueError(f"JSON root must be an object: {label}") + + values_seen = 0 + stack: list[tuple[object, int]] = [(document, 1)] + while stack: + value, depth = stack.pop() + values_seen += 1 + if values_seen > MAX_JSON_VALUES: + raise ValueError(f"JSON value count exceeds limit: {label}") + if depth > MAX_JSON_DEPTH: + raise ValueError(f"JSON nesting exceeds depth limit: {label}") + if isinstance(value, dict): + stack.extend((child, depth + 1) for child in value.values()) + elif isinstance(value, list): + stack.extend((child, depth + 1) for child in value) + elif isinstance(value, (int, float)) and not isinstance(value, bool): + try: + finite = math.isfinite(float(value)) + except (OverflowError, ValueError) as exc: + raise ValueError(f"non-finite JSON number: {label}") from exc + if not finite: + raise ValueError(f"non-finite JSON number: {label}") + return document + + +def safe_relative_path(name: str) -> bool: + if not isinstance(name, str) or not name or not name.isprintable(): + return False + posix = PurePosixPath(name) + windows = PureWindowsPath(name) + return ( + not name.startswith("/") + and "\\" not in name + and not windows.drive + and all(part not in ("", ".", "..") for part in posix.parts) + and posix.as_posix() == name + and unicodedata.normalize("NFC", name) == name + ) + + +def _scan_public_bytes(payload: bytes, label: str) -> None: + for description, pattern in (*PRIVATE_PATTERNS, *CREDENTIAL_PATTERNS): + if pattern.search(payload) is not None: + raise ValueError(f"{description} found in package file: {label}") + + +def _zip_mode(info: ZipInfo) -> int: + return (info.external_attr >> 16) & 0xFFFF + + +def _open_flags(*, directory: bool = False) -> int: + flags = os.O_RDONLY | getattr(os, "O_BINARY", 0) | getattr(os, "O_CLOEXEC", 0) + flags |= getattr(os, "O_NOFOLLOW", 0) + if directory: + flags |= getattr(os, "O_DIRECTORY", 0) + else: + flags |= getattr(os, "O_NONBLOCK", 0) + return flags + + +def _same_identity(left: os.stat_result, right: os.stat_result) -> bool: + return ( + left.st_dev, + left.st_ino, + stat.S_IFMT(left.st_mode), + ) == ( + right.st_dev, + right.st_ino, + stat.S_IFMT(right.st_mode), + ) + + +def _same_contents(left: os.stat_result, right: os.stat_result) -> bool: + return _same_identity(left, right) and ( + left.st_size, + left.st_mtime_ns, + left.st_ctime_ns, + ) == ( + right.st_size, + right.st_mtime_ns, + right.st_ctime_ns, + ) + + +def _read_regular_path(path: Path, maximum_bytes: int, label: str) -> bytes: + """Read one stable regular file without following a final-component symlink.""" + + try: + linked = path.lstat() + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise ValueError(f"{label} must be a regular non-symlink") + descriptor = os.open(path, _open_flags()) + except OSError as exc: + raise ValueError(f"cannot open {label}") from exc + try: + opened = os.fstat(descriptor) + if not _same_identity(linked, opened) or not stat.S_ISREG(opened.st_mode): + raise ValueError(f"{label} changed while opening") + if opened.st_size > maximum_bytes: + raise ValueError(f"{label} exceeds size limit") + payload = bytearray() + remaining = opened.st_size + 1 + while remaining: + chunk = os.read(descriptor, min(64 * 1024, remaining)) + if not chunk: + break + payload.extend(chunk) + remaining -= len(chunk) + closed_over = os.fstat(descriptor) + if not _same_contents(opened, closed_over) or len(payload) != opened.st_size: + raise ValueError(f"{label} changed while reading") + return bytes(payload) + finally: + os.close(descriptor) + + +def _read_zip(path: Path) -> dict[str, bytes]: + try: + linked = path.lstat() + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise ValueError("package ZIP must be a regular, non-symlink file") + descriptor = os.open(path, _open_flags()) + except OSError as exc: + raise ValueError("unable to open package ZIP") from exc + + files: dict[str, bytes] = {} + seen_archive_names: set[str] = set() + total = 0 + try: + opened = os.fstat(descriptor) + if not _same_identity(linked, opened) or not stat.S_ISREG(opened.st_mode): + raise ValueError("package ZIP changed while opening") + if opened.st_size > MAX_ARCHIVE_BYTES: + raise ValueError("package ZIP exceeds size limit") + with os.fdopen(descriptor, "rb", closefd=False) as source, ZipFile(source) as archive: + infos = archive.infolist() + if len(infos) > MAX_ARCHIVE_ENTRIES: + raise ValueError("ZIP exceeds entry-count limit") + for info in infos: + name = info.filename + if name in seen_archive_names: + raise ValueError(f"duplicate ZIP entry: {name!r}") + seen_archive_names.add(name) + if not safe_relative_path(name): + raise ValueError(f"unsafe ZIP path: {name!r}") + if info.is_dir() or name.endswith("/"): + raise ValueError(f"directory entries are not allowed: {name!r}") + if info.flag_bits & 0x1: + raise ValueError(f"encrypted ZIP entry is not allowed: {name!r}") + if info.compress_type not in (ZIP_STORED, ZIP_DEFLATED): + raise ValueError(f"unsupported ZIP compression: {name!r}") + mode = _zip_mode(info) + if stat.S_ISLNK(mode): + raise ValueError(f"symlink ZIP entry is not allowed: {name!r}") + if stat.S_IFMT(mode) not in (0, stat.S_IFREG): + raise ValueError(f"special-file ZIP entry is not allowed: {name!r}") + if mode & 0o111: + raise ValueError(f"executable ZIP entry is not allowed: {name!r}") + if info.file_size > MAX_ENTRY_BYTES: + raise ValueError(f"ZIP entry exceeds size limit: {name!r}") + if info.file_size and info.compress_size == 0: + raise ValueError(f"invalid ZIP compression size: {name!r}") + if ( + info.compress_size + and info.file_size > info.compress_size * MAX_COMPRESSION_RATIO + ): + raise ValueError(f"ZIP entry exceeds compression-ratio limit: {name!r}") + prefix = f"{PACKAGE_NAME}/" + if not name.startswith(prefix): + raise ValueError(f"unexpected ZIP package root: {name!r}") + relative = name[len(prefix) :] + if not relative or relative in files: + raise ValueError(f"duplicate or empty package path: {name!r}") + if relative not in EXPECTED_FILES: + raise ValueError(f"unexpected package path: {relative!r}") + total += info.file_size + if total > MAX_TOTAL_BYTES: + raise ValueError("ZIP exceeds total uncompressed size limit") + with archive.open(info) as stream: + payload = stream.read(info.file_size + 1) + if len(payload) != info.file_size: + raise ValueError(f"ZIP entry byte-count mismatch: {name!r}") + files[relative] = payload + after = os.fstat(descriptor) + if not _same_contents(opened, after): + raise ValueError("package ZIP changed while reading") + except (BadZipFile, EOFError, RuntimeError, zlib.error) as exc: + raise ValueError("invalid package ZIP") from exc + finally: + os.close(descriptor) + return files + + +def _read_directory(root: Path) -> dict[str, bytes]: + try: + linked = root.lstat() + if root.name != PACKAGE_NAME: + raise ValueError(f"unexpected extracted package root: {root.name!r}") + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISDIR(linked.st_mode): + raise ValueError("package root must be a real directory") + root_descriptor = os.open(root, _open_flags(directory=True)) + except OSError as exc: + raise ValueError("unable to open package root") from exc + + files: dict[str, bytes] = {} + total = 0 + entries_seen = 0 + + def walk(directory_descriptor: int, prefix: PurePosixPath | None = None) -> None: + nonlocal entries_seen, total + before = os.fstat(directory_descriptor) + with os.scandir(directory_descriptor) as entries: + for entry in entries: + entries_seen += 1 + if entries_seen > MAX_ARCHIVE_ENTRIES: + raise ValueError("package exceeds entry-count limit") + relative_path = ( + PurePosixPath(entry.name) + if prefix is None + else prefix / entry.name + ) + relative = relative_path.as_posix() + if not safe_relative_path(relative): + raise ValueError(f"unsafe package path: {relative!r}") + metadata = entry.stat(follow_symlinks=False) + if stat.S_ISDIR(metadata.st_mode): + if relative not in EXPECTED_DIRECTORIES: + raise ValueError(f"unexpected package directory: {relative!r}") + child_descriptor = os.open( + entry.name, _open_flags(directory=True), dir_fd=directory_descriptor + ) + try: + opened = os.fstat(child_descriptor) + if not _same_identity(metadata, opened) or not stat.S_ISDIR( + opened.st_mode + ): + raise ValueError( + f"package directory changed while opening: {relative!r}" + ) + walk(child_descriptor, relative_path) + finally: + os.close(child_descriptor) + continue + if not stat.S_ISREG(metadata.st_mode): + raise ValueError( + f"package entry must be a regular file: {relative!r}" + ) + if relative not in EXPECTED_FILES: + raise ValueError(f"unexpected package path: {relative!r}") + if metadata.st_mode & 0o111: + raise ValueError( + f"executable package entry is not allowed: {relative!r}" + ) + descriptor = os.open( + entry.name, _open_flags(), dir_fd=directory_descriptor + ) + try: + opened = os.fstat(descriptor) + if not _same_identity(metadata, opened) or not stat.S_ISREG( + opened.st_mode + ): + raise ValueError( + f"package entry changed while opening: {relative!r}" + ) + if opened.st_size > MAX_ENTRY_BYTES: + raise ValueError( + f"package entry exceeds size limit: {relative!r}" + ) + total += opened.st_size + if total > MAX_TOTAL_BYTES: + raise ValueError("package exceeds total size limit") + payload = bytearray() + remaining = opened.st_size + 1 + while remaining: + chunk = os.read(descriptor, min(64 * 1024, remaining)) + if not chunk: + break + payload.extend(chunk) + remaining -= len(chunk) + after = os.fstat(descriptor) + if not _same_contents(opened, after) or len(payload) != opened.st_size: + raise ValueError( + f"package entry changed while reading: {relative!r}" + ) + files[relative] = bytes(payload) + finally: + os.close(descriptor) + after = os.fstat(directory_descriptor) + if not _same_contents(before, after): + raise ValueError("package directory changed while reading") + + try: + opened_root = os.fstat(root_descriptor) + if not _same_identity(linked, opened_root) or not stat.S_ISDIR( + opened_root.st_mode + ): + raise ValueError("package root changed while opening") + walk(root_descriptor) + except OSError as exc: + raise ValueError("package changed or became inaccessible while reading") from exc + finally: + os.close(root_descriptor) + return files + + +def read_package(path: Path) -> dict[str, bytes]: + if path.suffix.casefold() == ".zip": + files = _read_zip(path) + elif path.is_dir(): + files = _read_directory(path) + else: + raise ValueError("package input must be a ZIP or extracted package root") + actual = frozenset(files) + if actual != EXPECTED_FILES: + missing = sorted(EXPECTED_FILES - actual) + unexpected = sorted(actual - EXPECTED_FILES) + raise ValueError( + f"package file set mismatch; missing={missing!r}, unexpected={unexpected!r}" + ) + for name, payload in files.items(): + _scan_public_bytes(payload, name) + return files + + +def verify_checksums(files: dict[str, bytes]) -> None: + payload = files[CHECKSUMS] + if len(payload) > MAX_TEXT_BYTES: + raise ValueError("nested checksum file exceeds size limit") + try: + text = payload.decode("utf-8") + except UnicodeDecodeError as exc: + raise ValueError("nested checksum file is not UTF-8") from exc + if not text.endswith("\n") or "\r" in text: + raise ValueError("nested checksum file must use newline-terminated LF records") + + declared: dict[str, str] = {} + for line in text.splitlines(): + match = CHECKSUM_LINE_RE.fullmatch(line) + if match is None: + raise ValueError(f"invalid nested checksum record: {line!r}") + digest, name = match.groups() + if not safe_relative_path(name): + raise ValueError(f"unsafe nested checksum path: {name!r}") + if name in declared: + raise ValueError(f"duplicate nested checksum path: {name!r}") + declared[name] = digest + + expected_names = EXPECTED_FILES - {CHECKSUMS} + if frozenset(declared) != expected_names: + raise ValueError("nested checksum coverage does not exactly match package files") + for name, expected in declared.items(): + actual = hashlib.sha256(files[name]).hexdigest() + if actual != expected: + raise ValueError(f"nested checksum mismatch: {name}") + + +def _require_list(document: dict, key: str, label: str) -> list: + value = document.get(key, []) + if not isinstance(value, list): + raise ValueError(f"{key} must be an array: {label}") + return value + + +def _index(value: object, count: int, label: str) -> int: + if not isinstance(value, int) or isinstance(value, bool) or not 0 <= value < count: + raise ValueError(f"out-of-range {label}") + return value + + +def _nonnegative_int(value: object, label: str, *, positive: bool = False) -> int: + minimum = 1 if positive else 0 + if ( + not isinstance(value, int) + or isinstance(value, bool) + or value < minimum + ): + raise ValueError(f"invalid {label}") + return value + + +def _walk_extensions(value: object, found: set[str]) -> None: + stack = [value] + while stack: + current = stack.pop() + if isinstance(current, dict): + for key, child in current.items(): + if key == "extensions": + if not isinstance(child, dict): + raise ValueError("GLB extensions value must be an object") + for extension_name in child: + if extension_name not in ALLOWED_EXTENSIONS: + raise ValueError( + f"unsupported GLB extension: {extension_name}" + ) + found.add(extension_name) + stack.append(child) + elif isinstance(current, list): + stack.extend(current) + elif isinstance(current, float) and not math.isfinite(current): + raise ValueError("non-finite GLB JSON number") + + +def _parse_glb(payload: bytes, label: str) -> tuple[dict, bytes]: + if len(payload) < 20: + raise ValueError(f"truncated GLB: {label}") + magic, version, declared_length = struct.unpack_from("<4sII", payload, 0) + if magic != b"glTF" or version != 2 or declared_length != len(payload): + raise ValueError(f"invalid GLB 2.0 header: {label}") + + chunks: list[tuple[int, bytes]] = [] + offset = 12 + while offset < len(payload): + if offset + 8 > len(payload): + raise ValueError(f"truncated GLB chunk header: {label}") + length, chunk_type = struct.unpack_from(" len(payload): + raise ValueError(f"truncated GLB chunk payload: {label}") + chunks.append((chunk_type, payload[offset:end])) + offset = end + if offset != len(payload) or len(chunks) != 2: + raise ValueError(f"GLB must contain exactly JSON and BIN chunks: {label}") + if chunks[0][0] != 0x4E4F534A or chunks[1][0] != 0x004E4942: + raise ValueError(f"invalid GLB chunk order or type: {label}") + + json_chunk = chunks[0][1] + stripped = json_chunk.rstrip(b" ") + if not stripped or any(byte != 0x20 for byte in json_chunk[len(stripped) :]): + raise ValueError(f"invalid GLB JSON padding: {label}") + document = load_json(stripped, f"{label} JSON chunk") + return document, chunks[1][1] + + +def _decode_accessor( + accessor_index: int, + accessors: list, + buffer_views: list, + binary: bytes, + label: str, +) -> tuple[dict, list[tuple[int | float, ...]]]: + accessor = accessors[accessor_index] + if not isinstance(accessor, dict): + raise ValueError(f"accessor must be an object: {label}") + if "sparse" in accessor: + raise ValueError(f"sparse accessors are not allowed: {label}") + view_index = _index(accessor.get("bufferView"), len(buffer_views), f"bufferView: {label}") + view = buffer_views[view_index] + component_type = accessor.get("componentType") + accessor_type = accessor.get("type") + if component_type not in COMPONENTS or accessor_type not in TYPE_COMPONENTS: + raise ValueError(f"unsupported accessor encoding: {label}") + component_bytes, format_character = COMPONENTS[component_type] + components = TYPE_COMPONENTS[accessor_type] + element_bytes = component_bytes * components + count = _nonnegative_int(accessor.get("count"), f"accessor count: {label}", positive=True) + if count > MAX_ACCESSOR_ELEMENTS: + raise ValueError(f"accessor count exceeds resource limit: {label}") + normalized = accessor.get("normalized", False) + if not isinstance(normalized, bool) or ( + normalized and component_type not in (5120, 5121, 5122, 5123) + ): + raise ValueError(f"invalid accessor normalization: {label}") + accessor_offset = _nonnegative_int(accessor.get("byteOffset", 0), f"accessor offset: {label}") + view_offset = _nonnegative_int(view.get("byteOffset", 0), f"bufferView offset: {label}") + view_length = _nonnegative_int( + view.get("byteLength"), f"bufferView length: {label}", positive=True + ) + stride = view.get("byteStride", element_bytes) + if ( + not isinstance(stride, int) + or isinstance(stride, bool) + or stride < element_bytes + or ("byteStride" in view and (stride < 4 or stride > 252 or stride % 4)) + ): + raise ValueError(f"invalid accessor stride: {label}") + absolute_offset = view_offset + accessor_offset + if absolute_offset % component_bytes: + raise ValueError(f"misaligned accessor: {label}") + required = accessor_offset + (count - 1) * stride + element_bytes + if required > view_length or view_offset + view_length > len(binary): + raise ValueError(f"accessor exceeds its bufferView: {label}") + + unpacker = struct.Struct("<" + format_character * components) + values = [ + unpacker.unpack_from(binary, absolute_offset + item * stride) + for item in range(count) + ] + if component_type == 5126 and any( + not math.isfinite(float(component)) for value in values for component in value + ): + raise ValueError(f"non-finite accessor value: {label}") + return accessor, values + + +def _validate_declared_bounds(accessor: dict, values: list[tuple], label: str) -> None: + for key, operation in (("min", min), ("max", max)): + if key not in accessor: + continue + declared = accessor[key] + components = len(values[0]) + if not isinstance(declared, list) or len(declared) != components: + raise ValueError(f"invalid accessor {key}: {label}") + for component in range(components): + actual = operation(value[component] for value in values) + expected = declared[component] + if not isinstance(expected, (int, float)) or isinstance(expected, bool): + raise ValueError(f"invalid accessor {key}: {label}") + try: + expected_number = float(expected) + except (OverflowError, ValueError) as exc: + raise ValueError(f"invalid accessor {key}: {label}") from exc + if not math.isfinite(expected_number): + raise ValueError(f"invalid accessor {key}: {label}") + tolerance = max(1e-6, abs(float(actual)) * 1e-6) + if not math.isclose(expected_number, float(actual), abs_tol=tolerance, rel_tol=1e-6): + raise ValueError(f"incorrect accessor {key}: {label}") + + +def _finite_number( + value: object, + label: str, + *, + minimum: float | None = None, + maximum: float | None = None, +) -> float: + if not isinstance(value, (int, float)) or isinstance(value, bool): + raise ValueError(f"invalid finite number: {label}") + try: + number = float(value) + except (OverflowError, ValueError) as exc: + raise ValueError(f"invalid finite number: {label}") from exc + if not math.isfinite(number): + raise ValueError(f"invalid finite number: {label}") + if minimum is not None and number < minimum: + raise ValueError(f"number below minimum: {label}") + if maximum is not None and number > maximum: + raise ValueError(f"number above maximum: {label}") + return number + + +def _finite_vector( + value: object, + length: int, + label: str, + *, + minimum: float | None = None, + maximum: float | None = None, +) -> tuple[float, ...]: + if not isinstance(value, list) or len(value) != length: + raise ValueError(f"invalid vector: {label}") + return tuple( + _finite_number(component, label, minimum=minimum, maximum=maximum) + for component in value + ) + + +Matrix4 = tuple[ + tuple[float, float, float, float], + tuple[float, float, float, float], + tuple[float, float, float, float], + tuple[float, float, float, float], +] +IDENTITY_MATRIX: Matrix4 = ( + (1.0, 0.0, 0.0, 0.0), + (0.0, 1.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, 0.0, 0.0, 1.0), +) + + +def _matrix_multiply(left: Matrix4, right: Matrix4) -> Matrix4: + result = tuple( + tuple( + sum(left[row][item] * right[item][column] for item in range(4)) + for column in range(4) + ) + for row in range(4) + ) # type: ignore[return-value] + if any(not math.isfinite(component) for row in result for component in row): + raise ValueError("node transform matrix overflowed finite bounds") + return result # type: ignore[return-value] + + +def _transform_point(matrix: Matrix4, point: tuple) -> tuple[float, float, float]: + homogeneous = tuple(float(component) for component in point) + (1.0,) + result = tuple( + sum(matrix[row][column] * homogeneous[column] for column in range(4)) + for row in range(4) + ) + if any(not math.isfinite(component) for component in result): + raise ValueError("node transform produced non-finite geometry") + if not math.isclose(result[3], 1.0, rel_tol=0.0, abs_tol=1e-7): + raise ValueError("node transform produced a non-affine geometry point") + return result[0], result[1], result[2] + + +def _node_transform_matrix(node: dict, label: str) -> Matrix4: + if "matrix" in node: + values = _finite_vector(node["matrix"], 16, f"node matrix: {label}") + matrix: Matrix4 = tuple( + tuple(values[column * 4 + row] for column in range(4)) + for row in range(4) + ) # type: ignore[assignment] + if any( + not math.isclose(matrix[3][column], expected, rel_tol=0.0, abs_tol=1e-7) + for column, expected in enumerate((0.0, 0.0, 0.0, 1.0)) + ): + raise ValueError(f"node matrix must be affine: {label}") + return matrix + + translation = _finite_vector( + node.get("translation", [0.0, 0.0, 0.0]), 3, f"node translation: {label}" + ) + rotation = _finite_vector( + node.get("rotation", [0.0, 0.0, 0.0, 1.0]), 4, f"node rotation: {label}" + ) + scale = _finite_vector( + node.get("scale", [1.0, 1.0, 1.0]), 3, f"node scale: {label}" + ) + quaternion_length = math.sqrt(sum(component * component for component in rotation)) + if not math.isclose( + quaternion_length, + 1.0, + rel_tol=QUATERNION_LENGTH_TOLERANCE, + abs_tol=QUATERNION_LENGTH_TOLERANCE, + ): + raise ValueError(f"node rotation quaternion is not normalized: {label}") + if any(abs(component) <= 1e-8 for component in scale): + raise ValueError(f"node scale collapses geometry: {label}") + + x, y, z, w = rotation + sx, sy, sz = scale + return ( + ( + (1.0 - 2.0 * (y * y + z * z)) * sx, + (2.0 * (x * y - z * w)) * sy, + (2.0 * (x * z + y * w)) * sz, + translation[0], + ), + ( + (2.0 * (x * y + z * w)) * sx, + (1.0 - 2.0 * (x * x + z * z)) * sy, + (2.0 * (y * z - x * w)) * sz, + translation[1], + ), + ( + (2.0 * (x * z - y * w)) * sx, + (2.0 * (y * z + x * w)) * sy, + (1.0 - 2.0 * (x * x + y * y)) * sz, + translation[2], + ), + (0.0, 0.0, 0.0, 1.0), + ) + + +def _glb_runtime_material(material: dict, label: str) -> RuntimeMaterial: + alpha_mode = material.get("alphaMode", "OPAQUE") + if alpha_mode not in ("OPAQUE", "MASK", "BLEND"): + raise ValueError(f"invalid material alphaMode: {label}") + double_sided = material.get("doubleSided", False) + if not isinstance(double_sided, bool): + raise ValueError(f"invalid material doubleSided: {label}") + pbr = material.get("pbrMetallicRoughness", {}) + if not isinstance(pbr, dict): + raise ValueError(f"invalid pbrMetallicRoughness: {label}") + if "baseColorTexture" in pbr or "metallicRoughnessTexture" in pbr: + raise ValueError(f"texture reference is not allowed in runtime material: {label}") + if any( + key in material for key in ("normalTexture", "occlusionTexture", "emissiveTexture") + ): + raise ValueError(f"texture reference is not allowed in runtime material: {label}") + base_color = _finite_vector( + pbr.get("baseColorFactor", [1.0, 1.0, 1.0, 1.0]), + 4, + f"baseColorFactor: {label}", + minimum=0.0, + maximum=1.0, + ) + emissive_factor = _finite_vector( + material.get("emissiveFactor", [0.0, 0.0, 0.0]), + 3, + f"emissiveFactor: {label}", + minimum=0.0, + maximum=1.0, + ) + extensions = material.get("extensions", {}) + if not isinstance(extensions, dict): + raise ValueError(f"invalid material extensions: {label}") + emissive_extension = extensions.get("KHR_materials_emissive_strength") + if emissive_extension is None: + # A zero factor has zero effective emission; otherwise glTF's strength + # default is 1.0 when the extension is absent. + emissive_strength = 0.0 if all(value == 0.0 for value in emissive_factor) else 1.0 + else: + if not isinstance(emissive_extension, dict): + raise ValueError(f"invalid emissive-strength extension: {label}") + emissive_strength = _finite_number( + emissive_extension.get("emissiveStrength", 1.0), + f"emissiveStrength: {label}", + minimum=0.0, + ) + return RuntimeMaterial( + name=material["name"], + alpha_mode=alpha_mode, + opaque=alpha_mode == "OPAQUE", + double_sided=double_sided, + base_color_factor=base_color, + metallic=_finite_number( + pbr.get("metallicFactor", 1.0), + f"metallicFactor: {label}", + minimum=0.0, + maximum=1.0, + ), + roughness=_finite_number( + pbr.get("roughnessFactor", 1.0), + f"roughnessFactor: {label}", + minimum=0.0, + maximum=1.0, + ), + emissive_factor=emissive_factor, + emissive_strength=emissive_strength, + ) + + +def inspect_glb( + payload: bytes, + label: str = "runtime GLB", + semantic_contract: GlbSemanticContract | None = None, +) -> GlbMetrics: + document, binary = _parse_glb(payload, label) + allowed_top_level = { + "accessors", + "asset", + "buffers", + "bufferViews", + "extensionsRequired", + "extensionsUsed", + "materials", + "meshes", + "nodes", + "scene", + "scenes", + } + if not set(document).issubset(allowed_top_level): + raise ValueError(f"unexpected GLB top-level field: {label}") + asset = document.get("asset") + if ( + not isinstance(asset, dict) + or not set(asset).issubset({"generator", "version"}) + or asset.get("version") != "2.0" + or not isinstance(asset.get("generator"), str) + or not asset["generator"] + ): + raise ValueError(f"GLB asset.version must be 2.0: {label}") + if semantic_contract is not None and asset.get("generator") != ( + "Khronos glTF Blender I/O v5.2.39" + ): + raise ValueError(f"GLB exporter identity does not match contract: {label}") + + extensions_used = _require_list(document, "extensionsUsed", label) + extensions_required = _require_list(document, "extensionsRequired", label) + if ( + any(not isinstance(item, str) for item in extensions_used + extensions_required) + or len(set(extensions_used)) != len(extensions_used) + or len(set(extensions_required)) != len(extensions_required) + or not set(extensions_used).issubset(ALLOWED_EXTENSIONS) + or not set(extensions_required).issubset(ALLOWED_EXTENSIONS) + or not set(extensions_required).issubset(set(extensions_used)) + ): + raise ValueError(f"unsupported or invalid GLB extension declaration: {label}") + found_extensions: set[str] = set() + _walk_extensions(document, found_extensions) + if found_extensions != set(extensions_used): + raise ValueError(f"GLB extension declarations do not match their use: {label}") + + forbidden_arrays = ("images", "textures", "samplers", "cameras", "skins", "animations") + for key in forbidden_arrays: + if _require_list(document, key, label): + raise ValueError(f"GLB must not contain {key}: {label}") + + def reject_uri(value: object) -> None: + stack = [value] + while stack: + current = stack.pop() + if isinstance(current, dict): + for key, child in current.items(): + if key == "uri": + raise ValueError( + f"external or embedded URI is not allowed: {label}" + ) + stack.append(child) + elif isinstance(current, list): + stack.extend(current) + + reject_uri(document) + + buffers = _require_list(document, "buffers", label) + if ( + len(buffers) != 1 + or not isinstance(buffers[0], dict) + or set(buffers[0]) != {"byteLength"} + ): + raise ValueError(f"GLB must contain exactly one embedded buffer: {label}") + embedded_bytes = _nonnegative_int( + buffers[0].get("byteLength"), f"embedded buffer byteLength: {label}", positive=True + ) + if embedded_bytes > len(binary) or len(binary) - embedded_bytes > 3: + raise ValueError(f"GLB BIN chunk length mismatch: {label}") + if any(binary[embedded_bytes:]): + raise ValueError(f"non-zero GLB BIN padding: {label}") + + buffer_views = _require_list(document, "bufferViews", label) + accessors = _require_list(document, "accessors", label) + if not buffer_views or not accessors: + raise ValueError(f"GLB geometry tables must be non-empty: {label}") + if len(buffer_views) > MAX_GLB_BUFFER_VIEWS or len(accessors) > MAX_GLB_ACCESSORS: + raise ValueError(f"GLB geometry tables exceed resource limits: {label}") + for index, view in enumerate(buffer_views): + if not isinstance(view, dict) or not set(view).issubset( + {"buffer", "byteLength", "byteOffset", "byteStride", "target"} + ): + raise ValueError(f"invalid bufferView buffer: {label} #{index}") + _index(view.get("buffer"), 1, f"bufferView buffer: {label} #{index}") + offset = _nonnegative_int(view.get("byteOffset", 0), f"bufferView offset: {label}") + length = _nonnegative_int( + view.get("byteLength"), f"bufferView length: {label}", positive=True + ) + if offset + length > embedded_bytes: + raise ValueError(f"bufferView exceeds embedded buffer: {label} #{index}") + if "target" in view and view["target"] not in (34962, 34963): + raise ValueError(f"invalid bufferView target: {label} #{index}") + + for index, accessor in enumerate(accessors): + if not isinstance(accessor, dict) or not set(accessor).issubset( + { + "bufferView", + "byteOffset", + "componentType", + "count", + "max", + "min", + "normalized", + "type", + } + ): + raise ValueError(f"invalid accessor field set: {label} #{index}") + + decoded: dict[int, tuple[dict, list[tuple]]] = {} + decoded_elements = 0 + + def decode(index: int) -> tuple[dict, list[tuple]]: + nonlocal decoded_elements + if index not in decoded: + decoded[index] = _decode_accessor(index, accessors, buffer_views, binary, label) + decoded_elements += len(decoded[index][1]) + if decoded_elements > MAX_DECODED_ACCESSOR_ELEMENTS: + raise ValueError(f"decoded accessors exceed resource limit: {label}") + _validate_declared_bounds(*decoded[index], f"{label} accessor {index}") + return decoded[index] + + materials = _require_list(document, "materials", label) + names: list[str] = [] + runtime_materials: list[RuntimeMaterial] = [] + for index, material in enumerate(materials): + if ( + not isinstance(material, dict) + or not set(material).issubset( + { + "alphaMode", + "doubleSided", + "emissiveFactor", + "extensions", + "extras", + "name", + "pbrMetallicRoughness", + } + ) + or not isinstance(material.get("name"), str) + ): + raise ValueError(f"invalid material record: {label} #{index}") + pbr = material.get("pbrMetallicRoughness", {}) + if not isinstance(pbr, dict) or not set(pbr).issubset( + {"baseColorFactor", "metallicFactor", "roughnessFactor"} + ): + raise ValueError(f"invalid material PBR field set: {label} #{index}") + extras = material.get("extras") + expected_extras = {"warpkeep_material_contract": material["name"]} + if extras is not None and not _strict_json_equal(extras, expected_extras): + raise ValueError(f"invalid material contract extras: {label} #{index}") + if semantic_contract is not None and extras is None: + raise ValueError(f"missing material contract extras: {label} #{index}") + extensions = material.get("extensions", {}) + emissive_extension = ( + extensions.get("KHR_materials_emissive_strength") + if isinstance(extensions, dict) + else None + ) + if emissive_extension is not None and ( + not isinstance(emissive_extension, dict) + or set(emissive_extension) != {"emissiveStrength"} + ): + raise ValueError(f"invalid emissive extension fields: {label} #{index}") + if material.get("alphaMode", "OPAQUE") != "OPAQUE": + raise ValueError(f"non-opaque material is not allowed: {label} #{index}") + names.append(material["name"]) + runtime_materials.append(_glb_runtime_material(material, f"{label} #{index}")) + if len(names) != 3 or frozenset(names) != EXPECTED_MATERIALS: + raise ValueError(f"Core Watcher material names do not match contract: {label}") + + meshes = _require_list(document, "meshes", label) + nodes = _require_list(document, "nodes", label) + scenes = _require_list(document, "scenes", label) + if ( + not meshes + or not nodes + or len(scenes) != 1 + or not _strict_json_equal(document.get("scene"), 0) + ): + raise ValueError(f"GLB must have one populated default scene: {label}") + if len(meshes) > MAX_GLB_MESHES or len(nodes) > MAX_GLB_NODES: + raise ValueError(f"GLB scene tables exceed resource limits: {label}") + + primitive_count = 0 + triangle_count = 0 + uploaded_vertices = 0 + mesh_positions: list[list[tuple]] = [[] for _ in meshes] + for mesh_index, mesh in enumerate(meshes): + if ( + not isinstance(mesh, dict) + or not set(mesh).issubset({"name", "primitives"}) + or not isinstance(mesh.get("primitives"), list) + ): + raise ValueError(f"invalid mesh: {label} #{mesh_index}") + if len(mesh["primitives"]) != 1 or "weights" in mesh: + raise ValueError( + f"every GLB mesh must contain exactly one primitive: {label} #{mesh_index}" + ) + for primitive_index, primitive in enumerate(mesh["primitives"]): + primitive_label = f"{label} mesh {mesh_index} primitive {primitive_index}" + if ( + not isinstance(primitive, dict) + or not set(primitive).issubset( + {"attributes", "indices", "material", "mode", "targets"} + ) + or primitive.get("mode", 4) != 4 + ): + raise ValueError(f"only triangle primitives are allowed: {primitive_label}") + if "targets" in primitive: + raise ValueError(f"morph targets are not allowed: {primitive_label}") + attributes = primitive.get("attributes") + if not isinstance(attributes, dict) or "POSITION" not in attributes: + raise ValueError(f"primitive requires POSITION: {primitive_label}") + if set(attributes) != {"POSITION", "NORMAL"}: + raise ValueError( + f"primitive attributes must be exactly POSITION and NORMAL: {primitive_label}" + ) + material_index = _index( + primitive.get("material"), len(materials), f"material: {primitive_label}" + ) + del material_index + + attribute_count: int | None = None + position_count = 0 + position_values: list[tuple] = [] + for semantic, accessor_reference in attributes.items(): + accessor_index = _index( + accessor_reference, len(accessors), f"attribute accessor: {primitive_label}" + ) + accessor, values = decode(accessor_index) + accessor_view = buffer_views[accessor["bufferView"]] + if accessor_view.get("target") != 34962: + raise ValueError(f"vertex accessor must target ARRAY_BUFFER: {primitive_label}") + if attribute_count is None: + attribute_count = len(values) + elif len(values) != attribute_count: + raise ValueError(f"vertex attribute count mismatch: {primitive_label}") + if semantic == "POSITION": + if accessor.get("componentType") != 5126 or accessor.get("type") != "VEC3": + raise ValueError(f"POSITION must be float VEC3: {primitive_label}") + if "min" not in accessor or "max" not in accessor: + raise ValueError(f"POSITION must declare bounds: {primitive_label}") + position_count = len(values) + position_values = values + mesh_positions[mesh_index].extend(values) + elif semantic == "NORMAL" and ( + accessor.get("componentType") != 5126 or accessor.get("type") != "VEC3" + ): + raise ValueError(f"NORMAL must be float VEC3: {primitive_label}") + elif semantic == "NORMAL": + for normal in values: + length = math.sqrt( + sum(float(component) * float(component) for component in normal) + ) + if not math.isclose( + length, + 1.0, + rel_tol=NORMAL_LENGTH_TOLERANCE, + abs_tol=NORMAL_LENGTH_TOLERANCE, + ): + raise ValueError( + f"NORMAL vector is not normalized: {primitive_label}" + ) + + indices_index = _index( + primitive.get("indices"), len(accessors), f"index accessor: {primitive_label}" + ) + index_accessor, index_values = decode(indices_index) + index_view = buffer_views[index_accessor["bufferView"]] + if ( + index_accessor.get("componentType") not in (5121, 5123, 5125) + or index_accessor.get("type") != "SCALAR" + or index_accessor.get("normalized", False) is not False + or index_view.get("target") != 34963 + or "byteStride" in index_view + or len(index_values) % 3 + ): + raise ValueError(f"invalid triangle index accessor: {primitive_label}") + flat_indices = [int(value[0]) for value in index_values] + if any(index >= position_count for index in flat_indices): + raise ValueError(f"out-of-range triangle index: {primitive_label}") + for offset in range(0, len(flat_indices), 3): + triangle_indices = flat_indices[offset : offset + 3] + if len(set(triangle_indices)) != 3: + raise ValueError(f"degenerate triangle index: {primitive_label}") + first, second, third = ( + position_values[index] for index in triangle_indices + ) + edge_a = tuple(second[axis] - first[axis] for axis in range(3)) + edge_b = tuple(third[axis] - first[axis] for axis in range(3)) + cross = ( + edge_a[1] * edge_b[2] - edge_a[2] * edge_b[1], + edge_a[2] * edge_b[0] - edge_a[0] * edge_b[2], + edge_a[0] * edge_b[1] - edge_a[1] * edge_b[0], + ) + cross_squared = sum(component * component for component in cross) + scale_squared = max( + sum(component * component for component in edge_a), + sum(component * component for component in edge_b), + sum( + (third[axis] - second[axis]) ** 2 + for axis in range(3) + ), + ) + if cross_squared <= max(1e-24, scale_squared * scale_squared * 1e-14): + raise ValueError(f"zero-area or collinear triangle: {primitive_label}") + primitive_count += 1 + triangle_count += len(flat_indices) // 3 + uploaded_vertices += position_count + + parents = [0] * len(nodes) + local_matrices: list[Matrix4] = [] + mesh_references = [0] * len(meshes) + for node_index, node in enumerate(nodes): + if not isinstance(node, dict) or not set(node).issubset( + { + "children", + "extras", + "matrix", + "mesh", + "name", + "rotation", + "scale", + "translation", + } + ): + raise ValueError(f"invalid node: {label} #{node_index}") + if any(key in node for key in ("camera", "skin", "weights")): + raise ValueError(f"non-rigid node is not allowed: {label} #{node_index}") + transform_shapes = {"translation": 3, "rotation": 4, "scale": 3, "matrix": 16} + if "matrix" in node and any(key in node for key in ("translation", "rotation", "scale")): + raise ValueError(f"node mixes matrix and TRS transforms: {label} #{node_index}") + for transform, expected_length in transform_shapes.items(): + if transform not in node: + continue + value = node[transform] + if ( + not isinstance(value, list) + or len(value) != expected_length + or any( + not isinstance(component, (int, float)) or isinstance(component, bool) + for component in value + ) + ): + raise ValueError(f"invalid node {transform}: {label} #{node_index}") + if "mesh" in node: + mesh_index = _index( + node["mesh"], len(meshes), f"node mesh: {label} #{node_index}" + ) + mesh_references[mesh_index] += 1 + children = node.get("children", []) + if ( + not isinstance(children, list) + or any(not isinstance(child, int) or isinstance(child, bool) for child in children) + or len(set(children)) != len(children) + ): + raise ValueError(f"invalid node children: {label} #{node_index}") + for child in children: + child_index = _index(child, len(nodes), f"node child: {label} #{node_index}") + parents[child_index] += 1 + if parents[child_index] > 1: + raise ValueError(f"node has multiple parents: {label} #{child_index}") + local_matrices.append(_node_transform_matrix(node, f"{label} #{node_index}")) + + if any(references != 1 for references in mesh_references): + raise ValueError(f"every GLB mesh must be referenced exactly once: {label}") + + scene = scenes[0] + if ( + not isinstance(scene, dict) + or not set(scene).issubset({"name", "nodes"}) + or not isinstance(scene.get("nodes"), list) + ): + raise ValueError(f"invalid default scene: {label}") + if semantic_contract is not None and scene.get("name") != "Scene": + raise ValueError(f"default scene name does not match contract: {label}") + roots = scene["nodes"] + if ( + not roots + or any(not isinstance(root, int) or isinstance(root, bool) for root in roots) + or len(set(roots)) != len(roots) + ): + raise ValueError(f"invalid default-scene roots: {label}") + visited: set[int] = set() + active: set[int] = set() + world_matrices: list[Matrix4 | None] = [None] * len(nodes) + + def visit(node_index: int, parent_matrix: Matrix4) -> None: + _index(node_index, len(nodes), f"scene node: {label}") + if node_index in active: + raise ValueError(f"cycle in node hierarchy: {label}") + if node_index in visited: + raise ValueError(f"node appears more than once in scene: {label}") + active.add(node_index) + world_matrix = _matrix_multiply(parent_matrix, local_matrices[node_index]) + world_matrices[node_index] = world_matrix + for child in nodes[node_index].get("children", []): + visit(child, world_matrix) + active.remove(node_index) + visited.add(node_index) + + for root in roots: + visit(root, IDENTITY_MATRIX) + if len(visited) != len(nodes): + raise ValueError(f"orphan node outside default scene: {label}") + + semantic = _extract_semantic_evidence(document, roots) + if semantic_contract is not None: + _verify_glb_semantic_contract(document, roots, semantic_contract, label) + + # Decode every accessor, including any not reached through a primitive. + for accessor_index in range(len(accessors)): + decode(accessor_index) + + world_positions: list[tuple[float, float, float]] = [] + for node_index, node in enumerate(nodes): + if "mesh" not in node: + continue + world_matrix = world_matrices[node_index] + if world_matrix is None: + raise ValueError(f"missing world transform for rendered node: {label}") + mesh_index = node["mesh"] + world_positions.extend( + _transform_point(world_matrix, position) + for position in mesh_positions[mesh_index] + ) + if not world_positions: + raise ValueError(f"GLB contains no rendered world-space geometry: {label}") + bounds_min = tuple( + min(position[axis] for position in world_positions) for axis in range(3) + ) + bounds_max = tuple( + max(position[axis] for position in world_positions) for axis in range(3) + ) + bounds_size = tuple( + bounds_max[axis] - bounds_min[axis] for axis in range(3) + ) + if any(size <= 0.0 for size in bounds_size): + raise ValueError(f"GLB world-space bounds are degenerate: {label}") + footprint_radius = max( + math.hypot(position[0], position[2]) for position in world_positions + ) + + return GlbMetrics( + bytes=len(payload), + sha256=hashlib.sha256(payload).hexdigest(), + triangles=triangle_count, + uploaded_vertices=uploaded_vertices, + embedded_buffer_bytes=embedded_bytes, + scenes=len(scenes), + nodes=len(nodes), + meshes=len(meshes), + primitives=primitive_count, + materials=len(materials), + images=len(document.get("images", [])), + textures=len(document.get("textures", [])), + samplers=len(document.get("samplers", [])), + cameras=len(document.get("cameras", [])), + skins=len(document.get("skins", [])), + animations=len(document.get("animations", [])), + extensions_used=tuple(extensions_used), + runtime_materials=tuple(runtime_materials), + bounds_gltf_min=bounds_min, + bounds_gltf_max=bounds_max, + bounds_gltf_size=bounds_size, + footprint_radius=footprint_radius, + semantic=semantic, + ) + + +def _strict_json_equal(actual: object, expected: object) -> bool: + if type(actual) is not type(expected): + return False + if isinstance(expected, dict): + return actual.keys() == expected.keys() and all( + _strict_json_equal(actual[key], expected[key]) for key in expected + ) + if isinstance(expected, list): + return len(actual) == len(expected) and all( + _strict_json_equal(left, right) for left, right in zip(actual, expected) + ) + return actual == expected + + +def _expect(document: dict, key: str, expected: object, label: str) -> None: + if key not in document or not _strict_json_equal(document[key], expected): + raise ValueError(f"unexpected {key} in {label}") + + +def _expect_exact_keys(document: object, expected: set[str], label: str) -> dict: + if not isinstance(document, dict) or set(document) != expected: + raise ValueError(f"unexpected field set in {label}") + return document + + +def load_integration_semantic_contracts( + path: Path = INTEGRATION_PROFILE_PATH, +) -> dict[str, GlbSemanticContract]: + """Load the digest-bound semantic declarations used by the runtime GLBs.""" + + payload = _read_regular_path( + path, MAX_JSON_BYTES, "tracked integration profile" + ) + profile = load_json(payload, str(path)) + if profile.get("schema") != "warpkeep.asset-integration-profile.v1": + raise ValueError("unexpected integration profile schema") + identity = profile.get("identity") + if not isinstance(identity, dict) or identity.get("assetId") != ASSET_ID: + raise ValueError("integration profile asset identity does not match") + + digest_record = _expect_exact_keys( + profile.get("contractDigest"), + {"algorithm", "canonicalization", "sha256"}, + "integration profile contractDigest", + ) + _expect(digest_record, "algorithm", "sha256", "integration profile contractDigest") + _expect( + digest_record, + "canonicalization", + ( + "exact tracked UTF-8 bytes with the 64 lowercase hexadecimal characters " + "at $.contractDigest.sha256 replaced by 64 ASCII zeroes; no other " + "transformation" + ), + "integration profile contractDigest", + ) + declared_digest = digest_record.get("sha256") + if not isinstance(declared_digest, str) or not SHA256_RE.fullmatch(declared_digest): + raise ValueError("invalid integration profile contract digest") + digest_bytes = declared_digest.encode("ascii") + if payload.count(digest_bytes) != 1: + raise ValueError("integration profile digest must occur exactly once") + canonical_payload = payload.replace(digest_bytes, b"0" * 64, 1) + if hashlib.sha256(canonical_payload).hexdigest() != declared_digest: + raise ValueError("integration profile contract digest mismatch") + if declared_digest != INTEGRATION_PROFILE_SHA256: + raise ValueError("integration profile digest is not the production-pinned digest") + + records = profile.get("profiles") + if not isinstance(records, list) or len(records) != len(LOD_CONTRACT): + raise ValueError("integration profile must declare exactly four profiles") + profile_ids = ("high", "balanced", "compact", "map") + contracts: dict[str, GlbSemanticContract] = {} + for record, profile_id, (tier, _, _, _) in zip( + records, profile_ids, LOD_CONTRACT + ): + record = _expect_exact_keys( + record, + { + "boundsGltfMeters", + "bytes", + "drawCalls", + "embeddedBufferBytes", + "file", + "id", + "materials", + "meshes", + "nodes", + "onePrimitivePerMesh", + "partNodes", + "primitives", + "rootNode", + "sha256", + "tier", + "triangles", + "uploadedVertices", + }, + f"integration profile {tier}", + ) + _expect(record, "id", profile_id, f"integration profile {tier}") + _expect(record, "tier", tier, f"integration profile {tier}") + _expect( + record, + "rootNode", + f"Warpkeep_CoreWatcher_Level1_{tier}", + f"integration profile {tier}", + ) + _expect(record, "onePrimitivePerMesh", True, f"integration profile {tier}") + part_nodes = record.get("partNodes") + if ( + not isinstance(part_nodes, list) + or not part_nodes + or any(not isinstance(name, str) or not name for name in part_nodes) + or len(set(part_nodes)) != len(part_nodes) + or part_nodes != sorted(part_nodes) + ): + raise ValueError(f"integration profile {tier} partNodes must be sorted and unique") + for key, expected in ( + ("nodes", len(part_nodes) + 1), + ("meshes", len(part_nodes)), + ("primitives", len(part_nodes)), + ("drawCalls", len(part_nodes)), + ): + _expect(record, key, expected, f"integration profile {tier}") + contracts[tier] = GlbSemanticContract( + tier=tier, + profile_id=profile_id, + root_node=record["rootNode"], + part_nodes=tuple(part_nodes), + ) + return contracts + + +def _expected_semantic_role(node_name: str) -> str: + if node_name.startswith("CoreWatcher_CoreCage_"): + return "core-cage" + if node_name.startswith(("CoreWatcher_FloatingShard_", "CoreWatcher_GroundShard_")): + return "floating-shard" + if node_name.startswith("CoreWatcher_GroundFracture_"): + return "ground-sigil" + if node_name == "CoreWatcher_SuspendedCore": + return "suspended-core" + if node_name in { + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_Footprint", + "CoreWatcher_LowerPedestal", + }: + return node_name + raise ValueError(f"unknown Core Watcher semantic part: {node_name!r}") + + +def _expected_part_material(node_name: str) -> str: + if node_name.startswith("CoreWatcher_GroundFracture_") or node_name in { + "CoreWatcher_CoreCage_2", + "CoreWatcher_SuspendedCore", + }: + return "WK_Core_Ultraviolet" + if node_name in { + "CoreWatcher_CoreCage_1", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_3", + "CoreWatcher_LowerPedestal", + }: + return "WK_Core_BlackenedMetal" + if node_name in { + "CoreWatcher_BifurcatedBody_Left", + "CoreWatcher_BifurcatedBody_Right", + "CoreWatcher_FloatingShard_2", + "CoreWatcher_Footprint", + } or node_name.startswith("CoreWatcher_GroundShard_"): + return "WK_Core_Obsidian" + raise ValueError(f"unknown Core Watcher material assignment: {node_name!r}") + + +def _extract_semantic_evidence( + document: dict, roots: list[int] +) -> GlbSemanticEvidence: + nodes = document["nodes"] + meshes = document["meshes"] + materials = document["materials"] + root_name = "" + if len(roots) == 1: + candidate = nodes[roots[0]].get("name") + if isinstance(candidate, str): + root_name = candidate + part_nodes: list[str] = [] + roles: list[tuple[str, str]] = [] + assignments: list[tuple[str, str]] = [] + for node in nodes: + if "mesh" not in node: + continue + name = node.get("name") if isinstance(node.get("name"), str) else "" + extras = node.get("extras") + role = ( + extras.get("warpkeep_semantic_role") + if isinstance(extras, dict) + and isinstance(extras.get("warpkeep_semantic_role"), str) + else "" + ) + mesh = meshes[node["mesh"]] + material_index = mesh["primitives"][0]["material"] + material_name = materials[material_index]["name"] + part_nodes.append(name) + roles.append((name, role)) + assignments.append((name, material_name)) + return GlbSemanticEvidence( + root_node=root_name, + part_nodes=tuple(part_nodes), + semantic_roles=tuple(roles), + material_assignments=tuple(assignments), + ) + + +def _verify_glb_semantic_contract( + document: dict, + roots: list[int], + contract: GlbSemanticContract, + label: str, +) -> None: + nodes = document["nodes"] + meshes = document["meshes"] + materials = document["materials"] + part_count = len(contract.part_nodes) + if len(nodes) != part_count + 1 or len(meshes) != part_count: + raise ValueError(f"semantic node and mesh counts do not match {contract.tier}: {label}") + if roots != [part_count]: + raise ValueError(f"{contract.tier} must have one exact semantic root: {label}") + root = nodes[part_count] + if set(root) != {"children", "extras", "name"}: + raise ValueError(f"{contract.tier} root node fields are not exact: {label}") + if root.get("name") != contract.root_node: + raise ValueError(f"{contract.tier} root node name does not match contract: {label}") + expected_root_extras = { + "warpkeep_asset_id": ASSET_ID, + "warpkeep_enemy_kind": "core-watcher", + "warpkeep_encounter_level": 1, + "warpkeep_state": "dormant-presence", + "warpkeep_combat_enabled": False, + "warpkeep_lod": contract.tier, + } + if not _strict_json_equal(root.get("extras"), expected_root_extras): + raise ValueError(f"{contract.tier} root extras do not match contract: {label}") + if root.get("children") != list(range(part_count)): + raise ValueError(f"{contract.tier} hierarchy must be flat root-to-parts: {label}") + + rendered_names = tuple(node.get("name") for node in nodes[:part_count]) + if rendered_names != contract.part_nodes or len(set(rendered_names)) != part_count: + raise ValueError(f"{contract.tier} part node list does not match contract: {label}") + for index, expected_name in enumerate(contract.part_nodes): + node = nodes[index] + if node.get("mesh") != index or "children" in node: + raise ValueError(f"{contract.tier} hierarchy must be flat root-to-parts: {label}") + expected_extras = { + "warpkeep_semantic_role": _expected_semantic_role(expected_name) + } + if not _strict_json_equal(node.get("extras"), expected_extras): + raise ValueError( + f"{contract.tier} semantic role does not match for {expected_name}: {label}" + ) + mesh = meshes[index] + if set(mesh) != {"name", "primitives"}: + raise ValueError(f"{contract.tier} mesh fields are not exact: {label}") + if mesh.get("name") != f"{expected_name}_Mesh": + raise ValueError( + f"{contract.tier} mesh name does not match for {expected_name}: {label}" + ) + primitive = mesh["primitives"][0] + material_index = primitive["material"] + material_name = materials[material_index]["name"] + if material_name != _expected_part_material(expected_name): + raise ValueError( + f"{contract.tier} material assignment does not match for " + f"{expected_name}: {label}" + ) + + +def _declared_runtime_material(record: object, label: str) -> RuntimeMaterial: + expected_keys = { + "name", + "alphaMode", + "opaque", + "doubleSided", + "baseColorFactor", + "metallic", + "roughness", + "emissiveFactor", + "emissiveStrength", + } + if not isinstance(record, dict) or set(record) != expected_keys: + raise ValueError(f"runtime material field set does not match contract: {label}") + name = record["name"] + alpha_mode = record["alphaMode"] + opaque = record["opaque"] + double_sided = record["doubleSided"] + if not isinstance(name, str) or name not in EXPECTED_MATERIALS: + raise ValueError(f"invalid runtime material name: {label}") + if alpha_mode not in ("OPAQUE", "MASK", "BLEND"): + raise ValueError(f"invalid declared material alphaMode: {label}") + if not isinstance(opaque, bool) or opaque is not (alpha_mode == "OPAQUE"): + raise ValueError(f"declared opaque/alphaMode mismatch: {label}") + if not isinstance(double_sided, bool): + raise ValueError(f"invalid declared material doubleSided: {label}") + return RuntimeMaterial( + name=name, + alpha_mode=alpha_mode, + opaque=opaque, + double_sided=double_sided, + base_color_factor=_finite_vector( + record["baseColorFactor"], + 4, + f"declared baseColorFactor: {label}", + minimum=0.0, + maximum=1.0, + ), + metallic=_finite_number( + record["metallic"], + f"declared metallic: {label}", + minimum=0.0, + maximum=1.0, + ), + roughness=_finite_number( + record["roughness"], + f"declared roughness: {label}", + minimum=0.0, + maximum=1.0, + ), + emissive_factor=_finite_vector( + record["emissiveFactor"], + 3, + f"declared emissiveFactor: {label}", + minimum=0.0, + maximum=1.0, + ), + emissive_strength=_finite_number( + record["emissiveStrength"], + f"declared emissiveStrength: {label}", + minimum=0.0, + ), + ) + + +def _runtime_materials_close( + declared: RuntimeMaterial, emitted: RuntimeMaterial +) -> bool: + if ( + declared.name != emitted.name + or declared.alpha_mode != emitted.alpha_mode + or declared.opaque is not emitted.opaque + or declared.double_sided is not emitted.double_sided + ): + return False + + def close(left: float, right: float) -> bool: + return math.isclose(left, right, rel_tol=1e-6, abs_tol=1e-7) + + return ( + all( + close(left, right) + for left, right in zip( + declared.base_color_factor, emitted.base_color_factor + ) + ) + and close(declared.metallic, emitted.metallic) + and close(declared.roughness, emitted.roughness) + and all( + close(left, right) + for left, right in zip(declared.emissive_factor, emitted.emissive_factor) + ) + and close(declared.emissive_strength, emitted.emissive_strength) + ) + + +def verify_material_contract( + manifest: dict, metrics: tuple[GlbMetrics, ...] +) -> None: + contract = _expect_exact_keys( + manifest.get("materialContract"), + { + "alphaBlendMaterials", + "authoringNote", + "heraldry", + "images", + "materials", + "palette", + "textures", + }, + "runtime materialContract", + ) + for key, expected in ( + ("alphaBlendMaterials", 0), + ("heraldry", "none"), + ("images", 0), + ("textures", 0), + ( + "authoringNote", + { + "note": ( + "Blender glTF export normalizes emissive color and strength; " + "the material records above are the emitted runtime values." + ), + "runtimeValuesDerivedFromExportedGlbs": True, + "ultravioletNodeEmissionStrength": 3.5, + }, + ), + ("palette", "obsidian, blackened metal, restrained cold ultraviolet"), + ): + _expect(contract, key, expected, "runtime materialContract") + + records = contract.get("materials") + if not isinstance(records, list) or len(records) != 3: + raise ValueError("runtime materialContract must contain exactly three materials") + declared_list = tuple( + _declared_runtime_material(record, f"runtime material #{index}") + for index, record in enumerate(records) + ) + declared = {material.name: material for material in declared_list} + if len(declared) != 3 or frozenset(declared) != EXPECTED_MATERIALS: + raise ValueError("runtime materialContract names must be exact and unique") + + for (tier, _, _, _), metric in zip(LOD_CONTRACT, metrics): + emitted = {material.name: material for material in metric.runtime_materials} + if frozenset(emitted) != EXPECTED_MATERIALS or len(emitted) != 3: + raise ValueError(f"{tier} emitted material names are not exact and unique") + for name in EXPECTED_MATERIALS: + if not _runtime_materials_close(declared[name], emitted[name]): + raise ValueError( + f"runtime materialContract differs from {tier} emitted {name} values" + ) + + +def _verify_bounds_blender(record: object, metric: GlbMetrics, tier: str) -> None: + bounds = _expect_exact_keys( + record, {"min", "max", "size"}, f"runtime LOD {tier} boundsBlender" + ) + declared_min = _finite_vector( + bounds["min"], 3, f"runtime LOD {tier} boundsBlender min" + ) + declared_max = _finite_vector( + bounds["max"], 3, f"runtime LOD {tier} boundsBlender max" + ) + declared_size = _finite_vector( + bounds["size"], + 3, + f"runtime LOD {tier} boundsBlender size", + minimum=0.0, + ) + for axis in range(3): + if declared_max[axis] <= declared_min[axis]: + raise ValueError(f"runtime LOD {tier} boundsBlender is degenerate") + actual_size = declared_max[axis] - declared_min[axis] + if not math.isclose( + declared_size[axis], actual_size, rel_tol=1e-6, abs_tol=1e-6 + ): + raise ValueError(f"runtime LOD {tier} boundsBlender size is inconsistent") + + # glTF exports Blender (X, Y, Z) into runtime (X, Z, -Y). Convert the + # emitted, hierarchy-transformed geometry back to authoring axes before + # comparing it with the source-scene envelope recorded by the builder. + emitted_blender_min = ( + metric.bounds_gltf_min[0], + -metric.bounds_gltf_max[2], + metric.bounds_gltf_min[1], + ) + emitted_blender_max = ( + metric.bounds_gltf_max[0], + -metric.bounds_gltf_min[2], + metric.bounds_gltf_max[1], + ) + for axis in range(3): + lower_margin = emitted_blender_min[axis] - declared_min[axis] + upper_margin = declared_max[axis] - emitted_blender_max[axis] + if lower_margin < -BOUND_TOLERANCE_METERS or upper_margin < -BOUND_TOLERANCE_METERS: + raise ValueError( + f"runtime LOD {tier} boundsBlender does not contain emitted glTF geometry" + ) + if ( + lower_margin > MAX_AUTHORING_BOUND_MARGIN_METERS + or upper_margin > MAX_AUTHORING_BOUND_MARGIN_METERS + ): + raise ValueError( + f"runtime LOD {tier} boundsBlender is too loose for emitted glTF geometry" + ) + + +def verify_runtime_manifest(files: dict[str, bytes]) -> tuple[GlbMetrics, ...]: + manifest = load_json(files[RUNTIME_MANIFEST], RUNTIME_MANIFEST) + _expect_exact_keys( + manifest, + { + "assetId", + "authoringCoordinateSystem", + "authorityBoundary", + "category", + "combatEnabled", + "coordinateSystem", + "encounterLevel", + "enemyKind", + "faction", + "frontFacing", + "lodGuidance", + "lods", + "materialContract", + "metersPerUnit", + "motion", + "name", + "pivot", + "revision", + "schema", + "selectionGuidance", + "state", + "version", + }, + "runtime manifest", + ) + for key, expected in ( + ("schema", "warpkeep.runtime-encounter-asset.v1"), + ("version", "1.0.0"), + ("revision", REVISION), + ("assetId", ASSET_ID), + ("authoringCoordinateSystem", "Blender, right-handed, +Z up, -Y front"), + ("category", "Encounters/Core/WatcherLevel1"), + ("coordinateSystem", "glTF 2.0, right-handed, +Y up, +Z forward"), + ("faction", "The Core"), + ("frontFacing", "+Z in glTF / -Y in Blender"), + ("name", "Core Watcher"), + ("enemyKind", "core-watcher"), + ("encounterLevel", 1), + ("combatEnabled", False), + ("authorityBoundary", AUTHORITY_BOUNDARY), + ("lodGuidance", RUNTIME_LOD_GUIDANCE), + ("metersPerUnit", 1.0), + ("motion", RUNTIME_MOTION_CONTRACT), + ("pivot", "footprint center on Blender Z=0 / glTF Y=0"), + ("selectionGuidance", RUNTIME_SELECTION_GUIDANCE), + ("state", "dormant-presence"), + ): + _expect(manifest, key, expected, "runtime manifest") + + lod_records = manifest.get("lods") + if not isinstance(lod_records, list) or len(lod_records) != len(LOD_CONTRACT): + raise ValueError("runtime manifest must contain exactly four LOD records") + + semantic_contracts = load_integration_semantic_contracts() + metrics: list[GlbMetrics] = [] + for record, (tier, filename, triangle_ceiling, byte_ceiling) in zip( + lod_records, LOD_CONTRACT + ): + record = _expect_exact_keys( + record, + { + "animations", + "boundsBlender", + "bytes", + "cameras", + "embeddedBufferBytes", + "extensionsUsed", + "externalUris", + "file", + "images", + "materials", + "meshes", + "nodes", + "primitives", + "rigged", + "samplers", + "scenes", + "sha256", + "skins", + "textures", + "tier", + "triangles", + "uploadedVertices", + }, + f"runtime LOD {tier}", + ) + _expect(record, "tier", tier, f"runtime LOD {tier}") + _expect(record, "file", filename, f"runtime LOD {tier}") + path = f"{RUNTIME_DIRECTORY}/{filename}" + payload = files[path] + if len(payload) > byte_ceiling: + raise ValueError(f"{tier} exceeds byte ceiling {byte_ceiling}") + metric = inspect_glb(payload, path, semantic_contracts[tier]) + if not 0 < metric.triangles <= triangle_ceiling: + raise ValueError(f"{tier} exceeds triangle ceiling {triangle_ceiling}") + expected_fields = { + "bytes": metric.bytes, + "sha256": metric.sha256, + "triangles": metric.triangles, + "uploadedVertices": metric.uploaded_vertices, + "embeddedBufferBytes": metric.embedded_buffer_bytes, + "scenes": metric.scenes, + "nodes": metric.nodes, + "meshes": metric.meshes, + "primitives": metric.primitives, + "materials": metric.materials, + "images": metric.images, + "textures": metric.textures, + "samplers": metric.samplers, + "cameras": metric.cameras, + "skins": metric.skins, + "animations": [], + "rigged": False, + "externalUris": [], + "extensionsUsed": list(metric.extensions_used), + } + for key, expected in expected_fields.items(): + _expect(record, key, expected, f"runtime LOD {tier}") + _verify_bounds_blender(record["boundsBlender"], metric, tier) + metrics.append(metric) + + triangles = [metric.triangles for metric in metrics] + byte_counts = [metric.bytes for metric in metrics] + if any(left <= right for left, right in zip(triangles, triangles[1:])): + raise ValueError("LOD triangles must be strictly descending") + if any(left <= right for left, right in zip(byte_counts, byte_counts[1:])): + raise ValueError("LOD bytes must be strictly descending") + result = tuple(metrics) + for (tier, _, _, _), metric in zip(LOD_CONTRACT, result): + if not math.isclose( + metric.bounds_gltf_min[1], + 0.0, + rel_tol=0.0, + abs_tol=BOUND_TOLERANCE_METERS, + ): + raise ValueError(f"{tier} emitted glTF geometry does not contact the ground") + if ( + metric.footprint_radius + > RUNTIME_SELECTION_GUIDANCE["presentationFootprintRadiusMeters"] + + BOUND_TOLERANCE_METERS + ): + raise ValueError(f"{tier} exceeds the presentation footprint radius") + if ( + metric.bounds_gltf_max[1] + > RUNTIME_SELECTION_GUIDANCE["suggestedPickCylinderHeightMeters"] + + BOUND_TOLERANCE_METERS + ): + raise ValueError(f"{tier} exceeds the suggested pick-cylinder height") + heights = [metric.bounds_gltf_size[1] for metric in result] + if max(heights) - min(heights) > 0.02: + raise ValueError("LOD emitted glTF heights are not stable") + verify_material_contract(manifest, result) + return result + + +def verify_asset_manifest(files: dict[str, bytes], metrics: tuple[GlbMetrics, ...]) -> None: + manifest = load_json(files[ASSET_MANIFEST], ASSET_MANIFEST) + _expect_exact_keys( + manifest, + { + "canonicalEditableSource", + "category", + "designIntent", + "faction", + "heroPreview", + "lodLineupPreview", + "mobilePreview", + "name", + "qaReport", + "revision", + "runtimeContracts", + "schema", + "sourceSemanticFingerprintSha256", + "status", + "transparentPreview", + "version", + "watcher", + }, + "asset manifest", + ) + for key, expected in ( + ("schema", "warpkeep.authoring-package.v1"), + ("version", "1.0.0"), + ("revision", REVISION), + ("category", "Encounters/Core/WatcherLevel1"), + ("faction", "The Core"), + ("name", "Warpkeep Core Watcher — Level 1"), + ("canonicalEditableSource", SOURCE_BLEND), + ("designIntent", RUNTIME_DESIGN_INTENT), + ("heroPreview", "Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg"), + ("lodLineupPreview", "Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg"), + ("transparentPreview", "Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png"), + ("mobilePreview", "Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png"), + ("qaReport", QA_REPORT), + ("runtimeContracts", RUNTIME_AUTHORING_CONTRACT), + ("status", "editable-static-runtime-validated-release-candidate"), + ): + _expect(manifest, key, expected, "asset manifest") + fingerprint = manifest.get("sourceSemanticFingerprintSha256") + if fingerprint != SOURCE_SEMANTIC_FINGERPRINT_SHA256: + raise ValueError("asset manifest source semantic fingerprint is not the pinned source") + watcher = _expect_exact_keys( + manifest.get("watcher"), + { + "assetId", + "combatEnabled", + "encounterLevel", + "enemyKind", + "name", + "runtimeManifest", + "source", + "state", + "triangles", + }, + "asset manifest watcher record", + ) + for key, expected in ( + ("assetId", ASSET_ID), + ("name", "Core Watcher"), + ("enemyKind", "core-watcher"), + ("encounterLevel", 1), + ("combatEnabled", False), + ("runtimeManifest", RUNTIME_MANIFEST), + ("source", SOURCE_BLEND), + ("state", "dormant-presence"), + ): + _expect(watcher, key, expected, "asset manifest watcher record") + expected_triangles = { + tier: metric.triangles + for (tier, _, _, _), metric in zip(LOD_CONTRACT, metrics) + } + _expect(watcher, "triangles", expected_triangles, "asset manifest watcher record") + + +def verify_qa(files: dict[str, bytes]) -> None: + report = load_json(files[QA_REPORT], QA_REPORT) + _expect_exact_keys( + report, + { + "budgets", + "checks", + "checksPassed", + "checksTotal", + "generatedAt", + "revision", + "schema", + "status", + }, + "runtime QA report", + ) + for key, expected in ( + ("schema", "warpkeep.runtime-qa.v1"), + ("revision", REVISION), + ("status", "passed"), + ("generatedAt", QA_GENERATED_AT), + ( + "budgets", + { + tier: {"triangles": triangle_ceiling, "bytes": byte_ceiling} + for tier, _, triangle_ceiling, byte_ceiling in LOD_CONTRACT + }, + ), + ): + _expect(report, key, expected, "runtime QA report") + checks = report.get("checks") + expected_records = [ + {"check": name, "passed": True} for name in EXPECTED_QA_CHECKS + ] + if not _strict_json_equal(checks, expected_records): + raise ValueError("runtime QA report must contain the exact 58 unique passed checks") + if not _strict_json_equal(report.get("checksTotal"), 58) or not _strict_json_equal( + report.get("checksPassed"), 58 + ): + raise ValueError("runtime QA report must declare exactly 58/58 checks passed") + + +def _png_dimensions(payload: bytes, label: str) -> tuple[int, int]: + if len(payload) < 8 or payload[:8] != b"\x89PNG\r\n\x1a\n": + raise ValueError(f"invalid PNG preview: {label}") + offset = 8 + dimensions: tuple[int, int] | None = None + pixel_format: tuple[int, int] | None = None + seen_idat = False + idat_ended = False + idat_parts: list[bytes] = [] + while offset < len(payload): + if offset + 12 > len(payload): + raise ValueError(f"truncated PNG chunk: {label}") + length = struct.unpack_from(">I", payload, offset)[0] + chunk_type = payload[offset + 4 : offset + 8] + end = offset + 12 + length + if end > len(payload): + raise ValueError(f"truncated PNG chunk payload: {label}") + if len(chunk_type) != 4 or any( + not (65 <= byte <= 90 or 97 <= byte <= 122) for byte in chunk_type + ): + raise ValueError(f"invalid PNG chunk type: {label}") + data = payload[offset + 8 : offset + 8 + length] + expected_crc = struct.unpack_from(">I", payload, offset + 8 + length)[0] + actual_crc = zlib.crc32(chunk_type + data) & 0xFFFFFFFF + if actual_crc != expected_crc: + raise ValueError(f"PNG chunk CRC mismatch: {label}") + if chunk_type in FORBIDDEN_PNG_CHUNKS: + raise ValueError( + f"forbidden PNG metadata chunk {chunk_type.decode('ascii')}: {label}" + ) + if chunk_type not in {b"IHDR", b"IDAT", b"IEND"}: + raise ValueError( + f"unexpected PNG ancillary or critical chunk " + f"{chunk_type.decode('ascii')}: {label}" + ) + + if dimensions is None and chunk_type != b"IHDR": + raise ValueError(f"PNG IHDR must be first: {label}") + if chunk_type == b"IHDR": + if dimensions is not None or length != 13: + raise ValueError(f"invalid PNG IHDR: {label}") + width, height, bit_depth, color_type, compression, filtering, interlace = ( + struct.unpack(">IIBBBBB", data) + ) + valid_depths = { + 0: {1, 2, 4, 8, 16}, + 2: {8, 16}, + 4: {8, 16}, + 6: {8, 16}, + } + if ( + width == 0 + or height == 0 + or bit_depth not in valid_depths.get(color_type, set()) + or compression != 0 + or filtering != 0 + or interlace != 0 + ): + raise ValueError(f"invalid PNG IHDR values: {label}") + dimensions = (width, height) + pixel_format = (bit_depth, color_type) + elif chunk_type == b"IDAT": + if idat_ended: + raise ValueError(f"non-contiguous PNG IDAT chunks: {label}") + seen_idat = True + idat_parts.append(data) + elif seen_idat: + idat_ended = True + + if chunk_type == b"IEND": + if length != 0 or not seen_idat or end != len(payload) or dimensions is None: + raise ValueError(f"invalid PNG IEND or trailing data: {label}") + if pixel_format is None: + raise ValueError(f"PNG pixel format is missing: {label}") + width, height = dimensions + bit_depth, color_type = pixel_format + channels = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[color_type] + row_bytes = (width * channels * bit_depth + 7) // 8 + expected_bytes = height * (row_bytes + 1) + if expected_bytes > MAX_PNG_DECOMPRESSED_BYTES: + raise ValueError(f"PNG decoded pixels exceed resource limit: {label}") + compressed = b"".join(idat_parts) + try: + decompressor = zlib.decompressobj() + decoded = decompressor.decompress(compressed, expected_bytes + 1) + except zlib.error as exc: + raise ValueError(f"invalid PNG IDAT stream: {label}") from exc + if ( + len(decoded) != expected_bytes + or decompressor.unconsumed_tail + or decompressor.unused_data + or not decompressor.eof + ): + raise ValueError(f"invalid or oversized PNG pixel stream: {label}") + if any( + decoded[row * (row_bytes + 1)] > 4 for row in range(height) + ): + raise ValueError(f"invalid PNG row filter: {label}") + return dimensions + offset = end + raise ValueError(f"PNG IEND is missing: {label}") + + +def _jpeg_dimensions(payload: bytes, label: str) -> tuple[int, int]: + if len(payload) < 4 or payload[:2] != b"\xff\xd8" or payload[-2:] != b"\xff\xd9": + raise ValueError(f"invalid JPEG preview: {label}") + offset = 2 + dimensions: tuple[int, int] | None = None + seen_scan = False + seen_jfif = False + quantization_tables: set[int] = set() + dc_huffman_tables: set[int] = set() + ac_huffman_tables: set[int] = set() + frame_components: tuple[int, ...] = () + allowed_segment_markers = {0xE0, 0xDB, 0xC0, 0xC4, 0xDA} + while offset < len(payload): + if payload[offset] != 0xFF: + raise ValueError(f"invalid JPEG marker stream: {label}") + while offset < len(payload) and payload[offset] == 0xFF: + offset += 1 + if offset >= len(payload): + break + marker = payload[offset] + offset += 1 + if marker == 0xD9: + if ( + offset != len(payload) + or dimensions is None + or not seen_scan + or not seen_jfif + ): + raise ValueError(f"invalid JPEG end marker or missing frame/scan: {label}") + return dimensions + if marker == 0xD8: + raise ValueError(f"unexpected JPEG start marker: {label}") + if marker == 0x00: + raise ValueError(f"stuffed JPEG byte outside scan data: {label}") + if seen_scan: + raise ValueError(f"unexpected JPEG marker after scan: {label}") + if marker == 0xFE or (0xE0 <= marker <= 0xEF and marker != 0xE0): + description = FORBIDDEN_JPEG_MARKERS.get(marker, f"APP{marker - 0xE0}") + raise ValueError( + f"forbidden JPEG metadata marker {description}: {label}" + ) + if marker not in allowed_segment_markers: + raise ValueError(f"unexpected JPEG marker 0x{marker:02x}: {label}") + if offset + 2 > len(payload): + raise ValueError(f"truncated JPEG marker: {label}") + segment_length = struct.unpack_from(">H", payload, offset)[0] + if segment_length < 2 or offset + segment_length > len(payload): + raise ValueError(f"invalid JPEG segment length: {label}") + segment_data = payload[offset + 2 : offset + segment_length] + if marker == 0xE0: + jfif = b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" + if seen_jfif or segment_data != jfif: + raise ValueError(f"invalid or duplicate JPEG JFIF header: {label}") + seen_jfif = True + if marker == 0xDB: + cursor = 0 + if not segment_data: + raise ValueError(f"empty JPEG quantization table segment: {label}") + while cursor < len(segment_data): + table_info = segment_data[cursor] + cursor += 1 + precision, table_id = table_info >> 4, table_info & 0x0F + table_bytes = 64 * (precision + 1) + if ( + precision not in (0, 1) + or table_id > 3 + or table_id in quantization_tables + or cursor + table_bytes > len(segment_data) + ): + raise ValueError(f"invalid JPEG quantization table: {label}") + table = segment_data[cursor : cursor + table_bytes] + values = ( + table + if precision == 0 + else struct.unpack(">" + "H" * 64, table) + ) + if any(value == 0 for value in values): + raise ValueError(f"zero JPEG quantization value: {label}") + quantization_tables.add(table_id) + cursor += table_bytes + if cursor != len(segment_data): + raise ValueError(f"invalid JPEG quantization table length: {label}") + if marker == 0xC4: + cursor = 0 + if not segment_data: + raise ValueError(f"empty JPEG Huffman table segment: {label}") + while cursor < len(segment_data): + if cursor + 17 > len(segment_data): + raise ValueError(f"truncated JPEG Huffman table: {label}") + table_info = segment_data[cursor] + table_class, table_id = table_info >> 4, table_info & 0x0F + counts = segment_data[cursor + 1 : cursor + 17] + symbol_count = sum(counts) + available_codes = 1 + for count in counts: + available_codes = available_codes * 2 - count + if available_codes < 0: + raise ValueError(f"oversubscribed JPEG Huffman table: {label}") + cursor += 17 + target = ( + dc_huffman_tables if table_class == 0 else ac_huffman_tables + ) + if ( + table_class not in (0, 1) + or table_id > 3 + or table_id in target + or not 0 < symbol_count <= 256 + or cursor + symbol_count > len(segment_data) + ): + raise ValueError(f"invalid JPEG Huffman table: {label}") + symbols = segment_data[cursor : cursor + symbol_count] + if ( + table_class == 0 + and any(symbol > 11 for symbol in symbols) + ) or ( + table_class == 1 + and any( + (symbol & 0x0F) > 10 + or ((symbol & 0x0F) == 0 and (symbol >> 4) not in (0, 15)) + for symbol in symbols + ) + ): + raise ValueError(f"invalid JPEG Huffman symbol: {label}") + target.add(table_id) + cursor += symbol_count + if cursor != len(segment_data): + raise ValueError(f"invalid JPEG Huffman table length: {label}") + if marker == 0xC0: + if segment_length < 8: + raise ValueError(f"invalid JPEG frame: {label}") + precision = segment_data[0] + height, width = struct.unpack_from(">HH", segment_data, 1) + components = segment_data[5] + if ( + precision != 8 + or components not in (1, 3) + or segment_length != 8 + 3 * components + or width == 0 + or height == 0 + or dimensions is not None + ): + raise ValueError(f"invalid or duplicate JPEG frame: {label}") + component_records = [ + segment_data[6 + index * 3 : 9 + index * 3] + for index in range(components) + ] + component_ids = tuple(record[0] for record in component_records) + if ( + len(set(component_ids)) != components + or any( + not (1 <= record[1] >> 4 <= 4) + or not (1 <= record[1] & 0x0F <= 4) + or record[2] not in quantization_tables + for record in component_records + ) + ): + raise ValueError(f"invalid JPEG frame components: {label}") + frame_components = component_ids + dimensions = (width, height) + segment_end = offset + segment_length + if marker != 0xDA: + offset = segment_end + continue + + if seen_scan or dimensions is None: + raise ValueError(f"invalid or duplicate JPEG scan: {label}") + if segment_length < 8: + raise ValueError(f"invalid JPEG scan header: {label}") + components = segment_data[0] + if components not in (1, 3) or segment_length != 6 + 2 * components: + raise ValueError(f"invalid JPEG scan header: {label}") + scan_records = [ + segment_data[1 + index * 2 : 3 + index * 2] + for index in range(components) + ] + if ( + tuple(record[0] for record in scan_records) != frame_components + or any( + record[1] >> 4 not in dc_huffman_tables + or record[1] & 0x0F not in ac_huffman_tables + for record in scan_records + ) + or segment_data[-3:] != b"\x00\x3f\x00" + ): + raise ValueError(f"invalid JPEG baseline scan contract: {label}") + seen_scan = True + offset = segment_end + saw_entropy_data = False + while offset < len(payload): + marker_start = payload.find(b"\xff", offset) + if marker_start < 0 or marker_start + 1 >= len(payload): + raise ValueError(f"unterminated JPEG scan data: {label}") + if marker_start > offset: + saw_entropy_data = True + cursor = marker_start + 1 + while cursor < len(payload) and payload[cursor] == 0xFF: + cursor += 1 + if cursor >= len(payload): + raise ValueError(f"unterminated JPEG marker fill: {label}") + escaped = payload[cursor] + if escaped == 0x00 or 0xD0 <= escaped <= 0xD7: + if escaped == 0x00: + saw_entropy_data = True + offset = cursor + 1 + continue + if not saw_entropy_data: + raise ValueError(f"JPEG scan contains no entropy-coded data: {label}") + offset = marker_start + break + raise ValueError(f"JPEG end marker is missing: {label}") + + +def verify_supporting_files(files: dict[str, bytes]) -> None: + blend = files[SOURCE_BLEND] + if ( + len(blend) < 17 + or blend[:7] != b"BLENDER" + or not blend[7:9].isdigit() + or blend[9:10] != b"-" + or not blend[10:12].isdigit() + or blend[12:13] != b"v" + or not blend[13:17].isdigit() + ): + raise ValueError("editable source has an invalid Blender header") + + for name in ("PACKAGE-NOTICE.md", "README.md"): + try: + text = files[name].decode("utf-8") + except UnicodeDecodeError as exc: + raise ValueError(f"invalid UTF-8 text file: {name}") from exc + if not text.strip(): + raise ValueError(f"required text file is empty: {name}") + + for name, dimensions in PREVIEW_DIMENSIONS.items(): + payload = files[name] + actual = ( + _png_dimensions(payload, name) + if name.endswith(".png") + else _jpeg_dimensions(payload, name) + ) + if actual != dimensions: + raise ValueError(f"preview dimensions mismatch: {name}") + + +def verify_package(path: Path | str) -> VerificationResult: + source = Path(path) + files = read_package(source) + verify_checksums(files) + metrics = verify_runtime_manifest(files) + verify_asset_manifest(files, metrics) + verify_qa(files) + verify_supporting_files(files) + return VerificationResult( + source=source, + files=len(files), + lods=len(metrics), + triangles=tuple(metric.triangles for metric in metrics), + bytes=tuple(metric.bytes for metric in metrics), + ) + + +def main() -> None: + parser = argparse.ArgumentParser( + description=( + "Structurally verify a Core Watcher Level 1 ZIP candidate or extracted " + "package root." + ), + epilog=( + "This check does not establish archive authenticity. Authenticate an exact " + "release with releases/core-watcher-level1-2026-08-03/manifest.json and " + "its SHA256SUMS.txt through scripts/verify_release.py." + ), + ) + parser.add_argument("package", type=Path, help="release-candidate ZIP or package root") + args = parser.parse_args() + try: + result = verify_package(args.package) + except (OSError, ValueError) as exc: + parser.exit(1, f"Core Watcher verification failed: {exc}\n") + triangle_summary = "/".join(str(value) for value in result.triangles) + print( + f"Structurally verified {PACKAGE_NAME}: {result.files} files, {result.lods} LODs, " + f"triangles {triangle_summary}." + ) + + +if __name__ == "__main__": + main() diff --git a/tests/test_verify_core_watcher_integration_profile.py b/tests/test_verify_core_watcher_integration_profile.py new file mode 100644 index 0000000..ba51adb --- /dev/null +++ b/tests/test_verify_core_watcher_integration_profile.py @@ -0,0 +1,398 @@ +from __future__ import annotations + +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +SPEC = importlib.util.spec_from_file_location( + "verify_core_watcher_integration_profile", + ROOT / "scripts" / "verify_core_watcher_integration_profile.py", +) +assert SPEC is not None and SPEC.loader is not None +verify = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = verify +SPEC.loader.exec_module(verify) + + +REQUIRED_FILES = ( + verify.PROFILE_PATH, + verify.RUNTIME_PATH, + verify.RELEASE_MANIFEST_PATH, + verify.CHECKSUM_SIDECAR_PATH, + verify.GALLERY_PATH, + Path("manifests/core-watcher-level1-2026-08-03.source.json"), + verify.PACKAGE_VERIFIER_PATH, + Path("previews/core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg"), + Path("previews/core-watcher-level1-2026-08-03/01-core-watcher-lod-lineup.jpg"), + Path("previews/core-watcher-level1-2026-08-03/02-core-watcher-map-preview.png"), +) + + +def copy_fixture(destination: Path) -> None: + for relative in REQUIRED_FILES: + target = destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, target) + + +def rewrite_profile(root: Path, mutate) -> str: + path = root / verify.PROFILE_PATH + document = json.loads(path.read_text(encoding="utf-8")) + mutate(document) + document["contractDigest"]["sha256"] = verify.ZERO_DIGEST + zeroed = ( + json.dumps(document, indent=2, ensure_ascii=False, allow_nan=False) + "\n" + ).encode("utf-8") + digest = hashlib.sha256(zeroed).hexdigest() + document["contractDigest"]["sha256"] = digest + path.write_text( + json.dumps(document, indent=2, ensure_ascii=False, allow_nan=False) + "\n", + encoding="utf-8", + ) + return digest + + +class IntegrationProfileHappyPathTests(unittest.TestCase): + def test_tracked_profile_and_cli(self) -> None: + self.assertEqual( + verify.verify(ROOT), + "0a34614dfb42f754fd2524b23ef213c2db502768ad9230bd6a27a9198a8251c0", + ) + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts" / "verify_core_watcher_integration_profile.py"), + "--root", + str(ROOT), + ], + capture_output=True, + text=True, + timeout=10, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn("no runtime, gameplay, release, or activation authority", result.stdout) + + +class IntegrationProfileMutationTests(unittest.TestCase): + def _mutated(self, mutate, pattern: str) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + digest = rewrite_profile(root, mutate) + original = verify.EXPECTED_PROFILE_DIGEST + verify.EXPECTED_PROFILE_DIGEST = digest + try: + with self.assertRaisesRegex(ValueError, pattern): + verify.verify(root) + finally: + verify.EXPECTED_PROFILE_DIGEST = original + + def test_digest_and_duplicate_keys_fail_closed(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + path = root / verify.PROFILE_PATH + raw = path.read_bytes().replace(b'"reviewOnly": true', b'"reviewOnly": false') + path.write_bytes(raw) + with self.assertRaisesRegex(ValueError, "digest mismatch"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + path = root / verify.PROFILE_PATH + raw = path.read_bytes().replace( + b' "assetBinding": {', + b' "assetBinding": {},\n "assetBinding": {', + 1, + ) + path.write_bytes(raw) + with self.assertRaisesRegex(ValueError, "duplicate JSON key"): + verify.verify(root) + + def test_digest_canonicalization_targets_only_the_declared_json_path(self) -> None: + declared = "a" * 64 + raw = ( + b'{"echo":"' + + declared.encode("ascii") + + b'","contractDigest":{"sha256":"' + + declared.encode("ascii") + + b'"}}' + ) + zeroed = verify._zeroed_contract_digest_bytes(raw, declared) + self.assertIn(b'"echo":"' + declared.encode("ascii") + b'"', zeroed) + self.assertIn(b'"sha256":"' + verify.ZERO_DIGEST.encode("ascii") + b'"', zeroed) + + escaped_value = raw.replace( + b'"sha256":"' + declared.encode("ascii"), + b'"sha256":"\\u0061' + declared[1:].encode("ascii"), + ) + with self.assertRaisesRegex(ValueError, "64 literal lowercase"): + verify._zeroed_contract_digest_bytes(escaped_value, declared) + + def test_deep_json_fails_closed_without_recursion_traceback(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + depth = verify.MAX_JSON_DEPTH + 1 + profile.write_text( + '{"nested":' + "[" * depth + "null" + "]" * depth + "}", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "JSON exceeds nesting limit"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + depth = 2000 + profile.write_text( + '{"nested":' + "[" * depth + "null" + "]" * depth + "}", + encoding="utf-8", + ) + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts" / "verify_core_watcher_integration_profile.py"), + "--root", + str(root), + ], + capture_output=True, + text=True, + timeout=10, + ) + self.assertEqual(result.returncode, 1) + self.assertIn("JSON exceeds nesting limit", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + def test_oversized_numeric_tokens_fail_before_conversion(self) -> None: + for token, pattern in ( + ("1" * (verify.MAX_JSON_NUMBER_CHARS + 1), "integer token exceeds"), + ( + "1." + "0" * verify.MAX_JSON_NUMBER_CHARS, + "floating-point token exceeds", + ), + ): + with self.subTest(pattern=pattern): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + profile.write_text('{"number":' + token + "}", encoding="utf-8") + with self.assertRaisesRegex(ValueError, pattern): + verify.verify(root) + + def test_self_consistent_reformat_still_requires_pinned_digest(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + path = root / verify.PROFILE_PATH + document = json.loads(path.read_text(encoding="utf-8")) + document["contractDigest"]["sha256"] = verify.ZERO_DIGEST + zeroed = ( + json.dumps(document, indent=4, sort_keys=True, ensure_ascii=False) + "\n" + ).encode("utf-8") + digest = hashlib.sha256(zeroed).hexdigest() + document["contractDigest"]["sha256"] = digest + path.write_text( + json.dumps(document, indent=4, sort_keys=True, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "production-pinned digest"): + verify.verify(root) + + def test_tracked_runtime_and_preview_bytes_are_bound(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + runtime = root / verify.RUNTIME_PATH + runtime.write_bytes(runtime.read_bytes() + b" ") + with self.assertRaisesRegex(ValueError, "tracked byte count mismatch: runtime manifest"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + preview = root / REQUIRED_FILES[-1] + payload = bytearray(preview.read_bytes()) + payload[-1] ^= 1 + preview.write_bytes(payload) + with self.assertRaisesRegex(ValueError, "preview bytes or SHA-256 mismatch"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + source = root / "manifests/core-watcher-level1-2026-08-03.source.json" + document = json.loads(source.read_text(encoding="utf-8")) + document["sanitization"]["sourceSemanticFingerprintSha256"] = "0" * 64 + source.write_text(json.dumps(document, indent=2) + "\n", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "tracked (byte count|SHA-256) mismatch"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + sidecar = root / verify.CHECKSUM_SIDECAR_PATH + sidecar.write_text( + "0" * 64 + + " warpkeep-core-watcher-level1-game-ready-2026-08-03-v1.zip\n", + encoding="ascii", + ) + with self.assertRaisesRegex(ValueError, "checksum sidecar"): + verify.verify(root) + + def test_preview_paths_must_be_canonical_and_contained(self) -> None: + cases = ( + "../00-core-watcher-presentation.jpg", + "previews//core-watcher-level1-2026-08-03/00-core-watcher-presentation.jpg", + "C:/outside/00-core-watcher-presentation.jpg", + "previews/core-watcher-level1-2026-08-03/00-core-watcher\npresentation.jpg", + "/".join("component" for _ in range(verify.MAX_TRACKED_PATH_COMPONENTS + 1)), + "x" * (verify.MAX_TRACKED_PATH_CHARS + 1), + ) + for path in cases: + with self.subTest(path=path): + self._mutated( + lambda value, path=path: value["presentation"]["previews"][0].__setitem__( + "path", path + ), + "unsafe tracked path", + ) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unavailable") + def test_tracked_files_reject_symlinks(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + preview = root / REQUIRED_FILES[-1] + other = root / REQUIRED_FILES[-2] + preview.unlink() + preview.symlink_to(other) + with self.assertRaisesRegex(ValueError, "regular non-symlink"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + target = root / verify.RUNTIME_PATH + profile.unlink() + profile.symlink_to(target) + with self.assertRaisesRegex(ValueError, "regular non-symlink"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + preview_directory = root / REQUIRED_FILES[-1].parent + moved_directory = preview_directory.with_name("moved-preview-directory") + preview_directory.rename(moved_directory) + preview_directory.symlink_to(moved_directory, target_is_directory=True) + with self.assertRaisesRegex(ValueError, "no symlink directories"): + verify.verify(root) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unavailable") + def test_untrusted_root_symlink_loop_fails_without_traceback(self) -> None: + with tempfile.TemporaryDirectory() as directory: + loop = Path(directory) / "loop" + loop.symlink_to(loop) + with self.assertRaisesRegex(ValueError, "cannot be resolved safely"): + verify.verify(loop) + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts" / "verify_core_watcher_integration_profile.py"), + "--root", + str(loop), + ], + capture_output=True, + text=True, + timeout=10, + ) + self.assertEqual(result.returncode, 1) + self.assertIn("cannot be resolved safely", result.stderr) + self.assertNotIn("Traceback", result.stderr) + + @unittest.skipUnless(hasattr(os, "mkfifo"), "FIFOs unavailable") + def test_tracked_special_file_fails_without_blocking(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + profile.unlink() + os.mkfifo(profile) + with self.assertRaisesRegex(ValueError, "regular non-symlink"): + verify.verify(root) + + def test_untrusted_root_cannot_supply_executable_helper(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + helper = root / verify.PACKAGE_VERIFIER_PATH + helper.write_text("raise AssertionError('untrusted helper executed')\n", encoding="utf-8") + self.assertEqual(verify.verify(root), verify.EXPECTED_PROFILE_DIGEST) + + def test_oversized_inputs_fail_before_reading(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + profile = root / verify.PROFILE_PATH + profile.write_bytes(b" " * (verify.MAX_PROFILE_BYTES + 1)) + with self.assertRaisesRegex(ValueError, "pre-read size limit"): + verify.verify(root) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + copy_fixture(root) + preview = root / REQUIRED_FILES[-1] + preview.write_bytes(b"x" * (verify.MAX_PREVIEW_BYTES + 1)) + with self.assertRaisesRegex(ValueError, "pre-read size limit"): + verify.verify(root) + + def test_authority_status_loading_and_unknown_fields_are_rejected(self) -> None: + cases = ( + (lambda value: value["authorityBoundary"].__setitem__("combat", True), "authority boundary"), + (lambda value: value["status"].__setitem__("activationAuthorized", True), "status gates"), + (lambda value: value["loading"].__setitem__("redirectsAllowed", True), "loading policy"), + (lambda value: value.__setitem__("privateAtlas", {}), "field set"), + ) + for mutate, pattern in cases: + with self.subTest(pattern=pattern): + self._mutated(mutate, pattern) + + def test_profile_bounds_nodes_and_capacity_math_are_rejected(self) -> None: + cases = ( + (lambda value: value["profiles"][0]["boundsGltfMeters"]["max"].__setitem__(0, 99.0), "emitted bounds"), + (lambda value: value["profiles"][3]["partNodes"].__setitem__(0, "Renamed"), "part nodes"), + (lambda value: value["instancing"]["capacityEvidence"].__setitem__("mapProfileVisibleTriangles", 1), "math mismatch"), + ) + for mutate, pattern in cases: + with self.subTest(pattern=pattern): + self._mutated(mutate, pattern) + + def test_modern_quality_motion_mobile_and_slice_guards_are_rejected(self) -> None: + cases = ( + (lambda value: value["qualityCamera"]["policies"]["reduced"].__setitem__("optionalAssetFetch", True), "quality/camera policy"), + (lambda value: value["motion"]["targets"][0].__setitem__("node", "Missing_Node"), "motion policy"), + (lambda value: value["fallbackAndAccessibility"]["testMatrix"]["safariIphone"].__setitem__("pageScroll", False), "fallback/accessibility policy"), + (lambda value: value["futureGameplaySlices"][0].__setitem__("implementedHere", True), "future gameplay slices"), + ) + for mutate, pattern in cases: + with self.subTest(pattern=pattern): + self._mutated(mutate, pattern) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_verify_core_watcher_level1.py b/tests/test_verify_core_watcher_level1.py new file mode 100644 index 0000000..269c6a1 --- /dev/null +++ b/tests/test_verify_core_watcher_level1.py @@ -0,0 +1,1349 @@ +from __future__ import annotations + +import copy +import hashlib +import importlib.util +import json +import os +from pathlib import Path +import stat +import struct +import subprocess +import sys +import tempfile +import unittest +import zlib +from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile, ZipInfo + + +ROOT = Path(__file__).resolve().parents[1] +SPEC = importlib.util.spec_from_file_location( + "verify_core_watcher_level1", + ROOT / "scripts" / "verify_core_watcher_level1.py", +) +assert SPEC is not None and SPEC.loader is not None +verify = importlib.util.module_from_spec(SPEC) +sys.modules[SPEC.name] = verify +SPEC.loader.exec_module(verify) + + +def glb_document(triangle_count: int) -> tuple[dict, bytes]: + positions: list[float] = [] + indices: list[int] = [] + for triangle in range(triangle_count): + x = float(triangle) * 0.01 + base = triangle * 3 + positions.extend((x, 0.0, 0.0, x + 0.25, 0.0, 0.0, x, 0.5, 0.1)) + indices.extend((base, base + 1, base + 2)) + position_bytes = struct.pack("<" + "f" * len(positions), *positions) + normal_values = [0.0, -0.196116135, 0.980580676] * (triangle_count * 3) + normal_bytes = struct.pack("<" + "f" * len(normal_values), *normal_values) + index_bytes = struct.pack("<" + "H" * len(indices), *indices) + binary = position_bytes + normal_bytes + index_bytes + document = { + "asset": {"version": "2.0", "generator": "Warpkeep verifier test"}, + "scene": 0, + "scenes": [{"nodes": [0]}], + "nodes": [{"mesh": 0, "name": "CoreWatcher_Test"}], + "meshes": [ + { + "primitives": [ + { + "attributes": {"POSITION": 0, "NORMAL": 1}, + "indices": 2, + "material": 0, + "mode": 4, + } + ] + } + ], + "materials": [ + {"name": "WK_Core_Obsidian"}, + {"name": "WK_Core_BlackenedMetal"}, + { + "name": "WK_Core_Ultraviolet", + "extensions": { + "KHR_materials_emissive_strength": {"emissiveStrength": 3.5} + }, + }, + ], + "extensionsUsed": ["KHR_materials_emissive_strength"], + "buffers": [{"byteLength": len(binary)}], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": len(position_bytes), + "target": 34962, + }, + { + "buffer": 0, + "byteOffset": len(position_bytes), + "byteLength": len(normal_bytes), + "target": 34962, + }, + { + "buffer": 0, + "byteOffset": len(position_bytes) + len(normal_bytes), + "byteLength": len(index_bytes), + "target": 34963, + }, + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": triangle_count * 3, + "type": "VEC3", + "min": [0.0, 0.0, 0.0], + "max": [float((triangle_count - 1) * 0.01 + 0.25), 0.5, 0.1], + }, + { + "bufferView": 1, + "componentType": 5126, + "count": triangle_count * 3, + "type": "VEC3", + }, + { + "bufferView": 2, + "componentType": 5123, + "count": triangle_count * 3, + "type": "SCALAR", + }, + ], + } + return document, binary + + +def encode_glb(document: dict, binary: bytes) -> bytes: + json_payload = json.dumps( + document, sort_keys=True, separators=(",", ":"), ensure_ascii=False + ).encode("utf-8") + json_payload += b" " * (-len(json_payload) % 4) + binary += b"\x00" * (-len(binary) % 4) + length = 12 + 8 + len(json_payload) + 8 + len(binary) + return b"".join( + ( + struct.pack("<4sII", b"glTF", 2, length), + struct.pack(" bytes: + document, binary = glb_document(triangle_count) + return encode_glb(document, binary) + + +def semantic_glb_document( + tier: str, triangles_per_mesh: int = 1 +) -> tuple[dict, bytes]: + document, binary = glb_document(triangles_per_mesh) + contract = verify.load_integration_semantic_contracts()[tier] + document["asset"]["generator"] = "Khronos glTF Blender I/O v5.2.39" + for material in document["materials"]: + material["extras"] = {"warpkeep_material_contract": material["name"]} + + def role(node_name: str) -> str: + if node_name.startswith("CoreWatcher_CoreCage_"): + return "core-cage" + if node_name.startswith( + ("CoreWatcher_FloatingShard_", "CoreWatcher_GroundShard_") + ): + return "floating-shard" + if node_name.startswith("CoreWatcher_GroundFracture_"): + return "ground-sigil" + if node_name == "CoreWatcher_SuspendedCore": + return "suspended-core" + return node_name + + def material_index(node_name: str) -> int: + if node_name.startswith("CoreWatcher_GroundFracture_") or node_name in { + "CoreWatcher_CoreCage_2", + "CoreWatcher_SuspendedCore", + }: + return 2 + if node_name in { + "CoreWatcher_CoreCage_1", + "CoreWatcher_CrownRib_Left", + "CoreWatcher_CrownRib_Right", + "CoreWatcher_FloatingShard_1", + "CoreWatcher_FloatingShard_3", + "CoreWatcher_LowerPedestal", + }: + return 1 + return 0 + + template = document["meshes"][0]["primitives"][0] + meshes = [] + nodes = [] + for index, node_name in enumerate(contract.part_nodes): + primitive = copy.deepcopy(template) + primitive["material"] = material_index(node_name) + meshes.append( + {"name": f"{node_name}_Mesh", "primitives": [primitive]} + ) + nodes.append( + { + "extras": {"warpkeep_semantic_role": role(node_name)}, + "mesh": index, + "name": node_name, + } + ) + nodes.append( + { + "children": list(range(len(contract.part_nodes))), + "extras": { + "warpkeep_asset_id": verify.ASSET_ID, + "warpkeep_enemy_kind": "core-watcher", + "warpkeep_encounter_level": 1, + "warpkeep_state": "dormant-presence", + "warpkeep_combat_enabled": False, + "warpkeep_lod": tier, + }, + "name": contract.root_node, + } + ) + document["meshes"] = meshes + document["nodes"] = nodes + document["scenes"] = [{"name": "Scene", "nodes": [len(contract.part_nodes)]}] + return document, binary + + +def make_semantic_glb(tier: str, triangles_per_mesh: int = 1) -> bytes: + document, binary = semantic_glb_document(tier, triangles_per_mesh) + return encode_glb(document, binary) + + +def png_chunk(chunk_type: bytes, data: bytes) -> bytes: + return ( + struct.pack(">I", len(data)) + + chunk_type + + data + + struct.pack(">I", zlib.crc32(chunk_type + data) & 0xFFFFFFFF) + ) + + +def png_image( + width: int, height: int, metadata: tuple[tuple[bytes, bytes], ...] = () +) -> bytes: + # A compact but fully decodable one-bit grayscale image keeps hostile-image + # tests realistic without writing multi-megabyte RGBA fixtures repeatedly. + ihdr = struct.pack(">IIBBBBB", width, height, 1, 0, 0, 0, 0) + row = b"\x00" + b"\x00" * ((width + 7) // 8) + return b"".join( + ( + b"\x89PNG\r\n\x1a\n", + png_chunk(b"IHDR", ihdr), + *(png_chunk(chunk_type, data) for chunk_type, data in metadata), + png_chunk(b"IDAT", zlib.compress(row * height)), + png_chunk(b"IEND", b""), + ) + ) + + +def jpeg_frame(width: int, height: int) -> bytes: + frame = struct.pack(">BHHB", 8, height, width, 1) + b"\x01\x11\x00" + scan = b"\x01\x01\x00\x00\x3f\x00" + jfif = b"JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00" + quantization = b"\x00" + b"\x01" * 64 + dc_huffman = b"\x00" + b"\x01" + b"\x00" * 15 + b"\x00" + ac_huffman = b"\x10" + b"\x01" + b"\x00" * 15 + b"\x00" + # One-bit DC-zero and AC-EOB codes make each constant 8x8 block two zero + # bits. Pad the final entropy byte with ones, as required by JPEG. + entropy_bits = 2 * (((width + 7) // 8) * ((height + 7) // 8)) + entropy = b"\x00" * (entropy_bits // 8) + remainder = entropy_bits % 8 + if remainder: + entropy += bytes(((1 << (8 - remainder)) - 1,)) + return b"".join( + ( + b"\xff\xd8", + b"\xff\xe0" + struct.pack(">H", len(jfif) + 2) + jfif, + b"\xff\xdb" + + struct.pack(">H", len(quantization) + 2) + + quantization, + b"\xff\xc0" + struct.pack(">H", len(frame) + 2) + frame, + b"\xff\xc4" + + struct.pack(">H", len(dc_huffman) + 2) + + dc_huffman, + b"\xff\xc4" + + struct.pack(">H", len(ac_huffman) + 2) + + ac_huffman, + b"\xff\xda" + struct.pack(">H", len(scan) + 2) + scan, + entropy, + b"\xff\xd9", + ) + ) + + +def write_json(path: Path, value: object) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2) + "\n", encoding="utf-8") + + +def refresh_checksums(package: Path) -> None: + paths = sorted( + path + for path in package.rglob("*") + if path.is_file() and path.relative_to(package).as_posix() != verify.CHECKSUMS + ) + lines = [ + f"{hashlib.sha256(path.read_bytes()).hexdigest()} " + f"{path.relative_to(package).as_posix()}\n" + for path in paths + ] + (package / verify.CHECKSUMS).write_text("".join(lines), encoding="utf-8") + + +def make_package(parent: Path) -> Path: + package = parent / verify.PACKAGE_NAME + package.mkdir() + (package / "PACKAGE-NOTICE.md").write_text("# Core Watcher notice\n", encoding="utf-8") + (package / "README.md").write_text("# Core Watcher Level 1\n", encoding="utf-8") + + source = package / verify.SOURCE_BLEND + source.parent.mkdir(parents=True) + source.write_bytes(b"BLENDER17-01v0502" + b"\x00" * 32) + + previews = { + name: (png_image(*dimensions) if name.endswith(".png") else jpeg_frame(*dimensions)) + for name, dimensions in verify.PREVIEW_DIMENSIONS.items() + } + for name, payload in previews.items(): + path = package / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(payload) + + runtime_lods: list[dict] = [] + metrics = [] + for tier, filename, _, _ in verify.LOD_CONTRACT: + payload = make_semantic_glb(tier) + path = package / verify.RUNTIME_DIRECTORY / filename + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(payload) + metric = verify.inspect_glb(payload, filename) + metrics.append(metric) + bounds_blender_min = [ + metric.bounds_gltf_min[0], + -metric.bounds_gltf_max[2], + metric.bounds_gltf_min[1], + ] + bounds_blender_max = [ + metric.bounds_gltf_max[0], + -metric.bounds_gltf_min[2], + metric.bounds_gltf_max[1], + ] + runtime_lods.append( + { + "tier": tier, + "file": filename, + "bytes": metric.bytes, + "sha256": metric.sha256, + "triangles": metric.triangles, + "uploadedVertices": metric.uploaded_vertices, + "embeddedBufferBytes": metric.embedded_buffer_bytes, + "scenes": metric.scenes, + "nodes": metric.nodes, + "meshes": metric.meshes, + "primitives": metric.primitives, + "materials": metric.materials, + "images": metric.images, + "textures": metric.textures, + "samplers": metric.samplers, + "cameras": metric.cameras, + "skins": metric.skins, + "animations": [], + "rigged": False, + "externalUris": [], + "extensionsUsed": list(metric.extensions_used), + "boundsBlender": { + "min": bounds_blender_min, + "max": bounds_blender_max, + "size": [ + bounds_blender_max[axis] - bounds_blender_min[axis] + for axis in range(3) + ], + }, + } + ) + + runtime_manifest = { + "assetId": verify.ASSET_ID, + "authoringCoordinateSystem": "Blender, right-handed, +Z up, -Y front", + "authorityBoundary": verify.AUTHORITY_BOUNDARY, + "category": "Encounters/Core/WatcherLevel1", + "combatEnabled": False, + "coordinateSystem": "glTF 2.0, right-handed, +Y up, +Z forward", + "encounterLevel": 1, + "enemyKind": "core-watcher", + "faction": "The Core", + "frontFacing": "+Z in glTF / -Y in Blender", + "lodGuidance": verify.RUNTIME_LOD_GUIDANCE, + "lods": runtime_lods, + "metersPerUnit": 1.0, + "motion": verify.RUNTIME_MOTION_CONTRACT, + "name": "Core Watcher", + "pivot": "footprint center on Blender Z=0 / glTF Y=0", + "revision": verify.REVISION, + "schema": "warpkeep.runtime-encounter-asset.v1", + "selectionGuidance": verify.RUNTIME_SELECTION_GUIDANCE, + "state": "dormant-presence", + "version": "1.0.0", + "materialContract": { + "alphaBlendMaterials": 0, + "authoringNote": { + "note": ( + "Blender glTF export normalizes emissive color and strength; " + "the material records above are the emitted runtime values." + ), + "runtimeValuesDerivedFromExportedGlbs": True, + "ultravioletNodeEmissionStrength": 3.5, + }, + "heraldry": "none", + "images": 0, + "materials": [ + { + "name": material.name, + "alphaMode": material.alpha_mode, + "opaque": material.opaque, + "doubleSided": material.double_sided, + "baseColorFactor": list(material.base_color_factor), + "metallic": material.metallic, + "roughness": material.roughness, + "emissiveFactor": list(material.emissive_factor), + "emissiveStrength": material.emissive_strength, + } + for material in metrics[0].runtime_materials + ], + "palette": "obsidian, blackened metal, restrained cold ultraviolet", + "textures": 0, + }, + } + write_json(package / verify.RUNTIME_MANIFEST, runtime_manifest) + + asset_manifest = { + "canonicalEditableSource": verify.SOURCE_BLEND, + "category": "Encounters/Core/WatcherLevel1", + "designIntent": verify.RUNTIME_DESIGN_INTENT, + "faction": "The Core", + "heroPreview": "Previews/Warpkeep_CoreWatcher_Level1_Presentation_1920.jpg", + "lodLineupPreview": "Previews/Warpkeep_CoreWatcher_Level1_LOD_Lineup_2400.jpg", + "mobilePreview": "Previews/Mobile/Warpkeep_CoreWatcher_Level1_Map_512.png", + "name": "Warpkeep Core Watcher — Level 1", + "qaReport": verify.QA_REPORT, + "revision": verify.REVISION, + "runtimeContracts": verify.RUNTIME_AUTHORING_CONTRACT, + "schema": "warpkeep.authoring-package.v1", + "sourceSemanticFingerprintSha256": verify.SOURCE_SEMANTIC_FINGERPRINT_SHA256, + "status": "editable-static-runtime-validated-release-candidate", + "transparentPreview": "Previews/Warpkeep_CoreWatcher_Level1_Transparent_1600.png", + "version": "1.0.0", + "watcher": { + "assetId": verify.ASSET_ID, + "name": "Core Watcher", + "enemyKind": "core-watcher", + "encounterLevel": 1, + "combatEnabled": False, + "runtimeManifest": verify.RUNTIME_MANIFEST, + "source": verify.SOURCE_BLEND, + "state": "dormant-presence", + "triangles": { + contract[0]: metric.triangles + for contract, metric in zip(verify.LOD_CONTRACT, metrics) + }, + }, + } + write_json(package / verify.ASSET_MANIFEST, asset_manifest) + + checks = [ + {"check": name, "passed": True} for name in verify.EXPECTED_QA_CHECKS + ] + qa = { + "schema": "warpkeep.runtime-qa.v1", + "revision": verify.REVISION, + "status": "passed", + "generatedAt": verify.QA_GENERATED_AT, + "budgets": { + tier: {"triangles": triangle_ceiling, "bytes": byte_ceiling} + for tier, _, triangle_ceiling, byte_ceiling in verify.LOD_CONTRACT + }, + "checks": checks, + "checksTotal": len(checks), + "checksPassed": len(checks), + } + write_json(package / verify.QA_REPORT, qa) + refresh_checksums(package) + return package + + +def write_archive(package: Path, archive_path: Path, *, executable: str | None = None) -> None: + with ZipFile(archive_path, "w", compression=ZIP_STORED) as archive: + for path in sorted(item for item in package.rglob("*") if item.is_file()): + relative = path.relative_to(package).as_posix() + info = ZipInfo(f"{verify.PACKAGE_NAME}/{relative}") + info.compress_type = ZIP_STORED + mode = 0o755 if relative == executable else 0o644 + info.external_attr = (stat.S_IFREG | mode) << 16 + archive.writestr(info, path.read_bytes()) + + +def update_runtime_lod(package: Path, tier: str, payload: bytes) -> None: + runtime_path = package / verify.RUNTIME_MANIFEST + manifest = json.loads(runtime_path.read_text(encoding="utf-8")) + record = next(item for item in manifest["lods"] if item["tier"] == tier) + target = package / verify.RUNTIME_DIRECTORY / record["file"] + target.write_bytes(payload) + metric = verify.inspect_glb(payload, record["file"]) + bounds_blender_min = [ + metric.bounds_gltf_min[0], + -metric.bounds_gltf_max[2], + metric.bounds_gltf_min[1], + ] + bounds_blender_max = [ + metric.bounds_gltf_max[0], + -metric.bounds_gltf_min[2], + metric.bounds_gltf_max[1], + ] + record.update( + { + "bytes": metric.bytes, + "sha256": metric.sha256, + "triangles": metric.triangles, + "uploadedVertices": metric.uploaded_vertices, + "embeddedBufferBytes": metric.embedded_buffer_bytes, + "scenes": metric.scenes, + "nodes": metric.nodes, + "meshes": metric.meshes, + "primitives": metric.primitives, + "materials": metric.materials, + "images": metric.images, + "textures": metric.textures, + "samplers": metric.samplers, + "cameras": metric.cameras, + "skins": metric.skins, + "animations": [], + "rigged": False, + "externalUris": [], + "extensionsUsed": list(metric.extensions_used), + "boundsBlender": { + "min": bounds_blender_min, + "max": bounds_blender_max, + "size": [ + bounds_blender_max[axis] - bounds_blender_min[axis] + for axis in range(3) + ], + }, + } + ) + write_json(runtime_path, manifest) + asset_path = package / verify.ASSET_MANIFEST + asset_manifest = json.loads(asset_path.read_text(encoding="utf-8")) + asset_manifest["watcher"]["triangles"][tier] = metric.triangles + write_json(asset_path, asset_manifest) + refresh_checksums(package) + + +def mutate_runtime_glb(package: Path, tier: str, mutate) -> None: + filename = next( + filename + for contract_tier, filename, _, _ in verify.LOD_CONTRACT + if contract_tier == tier + ) + target = package / verify.RUNTIME_DIRECTORY / filename + document, binary = verify._parse_glb(target.read_bytes(), filename) + mutate(document) + target.write_bytes(encode_glb(document, binary)) + refresh_checksums(package) + + +class CoreWatcherHappyPathTests(unittest.TestCase): + def test_extracted_package(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + result = verify.verify_package(package) + self.assertEqual(result.files, 15) + self.assertEqual(result.triangles, (23, 20, 15, 12)) + + def test_zip_and_cli(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + package = make_package(root) + archive = root / "core-watcher.zip" + write_archive(package, archive) + result = subprocess.run( + [ + sys.executable, + str(ROOT / "scripts" / "verify_core_watcher_level1.py"), + str(archive), + ], + capture_output=True, + text=True, + timeout=10, + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertIn( + "Structurally verified Warpkeep_CoreWatcher_Level1_GameReady", + result.stdout, + ) + + +class PackageSafetyTests(unittest.TestCase): + def test_json_limits_fail_closed_without_recursion_or_numeric_crashes(self) -> None: + deeply_nested = b'{"value":' + b"[" * 1_100 + b"0" + b"]" * 1_100 + b"}" + with self.assertRaisesRegex(ValueError, "nesting exceeds depth limit"): + verify.load_json(deeply_nested, "nested.json") + + bounded_but_too_deep = ( + b'{"value":' + b"[" * (verify.MAX_JSON_DEPTH + 1) + + b"0" + b"]" * (verify.MAX_JSON_DEPTH + 1) + b"}" + ) + with self.assertRaisesRegex(ValueError, "nesting exceeds depth limit"): + verify.load_json(bounded_but_too_deep, "deep.json") + + long_number = b'{"value":' + b"1" * (verify.MAX_JSON_NUMBER_CHARS + 1) + b"}" + with self.assertRaisesRegex(ValueError, "token exceeds size limit"): + verify.load_json(long_number, "number.json") + + too_many_values = b'{"values":[' + b"null," * verify.MAX_JSON_VALUES + b"null]}" + self.assertLess(len(too_many_values), verify.MAX_JSON_BYTES) + with self.assertRaisesRegex(ValueError, "value count exceeds limit"): + verify.load_json(too_many_values, "wide.json") + + def test_zip_rejects_traversal_and_duplicate_entries(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + traversal = root / "traversal.zip" + with ZipFile(traversal, "w") as archive: + archive.writestr(f"{verify.PACKAGE_NAME}/../escape.txt", b"escape") + with self.assertRaisesRegex(ValueError, "unsafe ZIP path"): + verify.verify_package(traversal) + + duplicate = root / "duplicate.zip" + with ZipFile(duplicate, "w") as archive: + archive.writestr(f"{verify.PACKAGE_NAME}/README.md", b"one") + with self.assertWarns(UserWarning): + archive.writestr(f"{verify.PACKAGE_NAME}/README.md", b"two") + with self.assertRaisesRegex(ValueError, "duplicate ZIP entry"): + verify.verify_package(duplicate) + + def test_zip_rejects_executable_member(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + package = make_package(root) + archive = root / "executable.zip" + write_archive(package, archive, executable="README.md") + with self.assertRaisesRegex(ValueError, "executable ZIP entry"): + verify.verify_package(archive) + + def test_zip_wraps_malformed_deflate_as_a_verification_failure(self) -> None: + with tempfile.TemporaryDirectory() as directory: + archive_path = Path(directory) / "malformed.zip" + member = f"{verify.PACKAGE_NAME}/README.md" + payload = bytes(range(256)) * 40 + with ZipFile(archive_path, "w", compression=ZIP_DEFLATED) as archive: + archive.writestr(member, payload) + with ZipFile(archive_path) as archive: + info = archive.getinfo(member) + malformed = bytearray(archive_path.read_bytes()) + name_length, extra_length = struct.unpack_from( + " None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + readme = package / "README.md" + readme.unlink() + readme.symlink_to(package / "PACKAGE-NOTICE.md") + with self.assertRaisesRegex(ValueError, "regular file"): + verify.verify_package(package) + + @unittest.skipUnless(hasattr(os, "mkfifo"), "FIFOs unavailable") + def test_extracted_package_rejects_fifo_without_opening_it(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + readme = package / "README.md" + readme.unlink() + os.mkfifo(readme) + with self.assertRaisesRegex(ValueError, "regular file"): + verify.verify_package(package) + if hasattr(os, "O_NONBLOCK"): + self.assertTrue(verify._open_flags() & os.O_NONBLOCK) + + def test_extracted_package_rejects_unexpected_tree_before_reading_files(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + unexpected = package / "unexpected" + unexpected.mkdir() + # An unbounded walker would enumerate this entire hostile fanout. + for index in range(verify.MAX_ARCHIVE_ENTRIES + 20): + (unexpected / f"entry-{index}").touch() + with self.assertRaisesRegex(ValueError, "unexpected package directory"): + verify.verify_package(package) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unavailable") + def test_zip_input_must_not_be_a_symlink(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + package = make_package(root) + archive = root / "candidate.zip" + write_archive(package, archive) + linked = root / "linked.zip" + linked.symlink_to(archive) + with self.assertRaisesRegex(ValueError, "regular, non-symlink"): + verify.verify_package(linked) + + def test_nested_checksums_require_exact_coverage_and_bytes(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + checksum_path = package / verify.CHECKSUMS + lines = checksum_path.read_text(encoding="utf-8").splitlines() + checksum_path.write_text("\n".join(lines[:-1]) + "\n", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "coverage"): + verify.verify_package(package) + + refresh_checksums(package) + (package / "README.md").write_text("changed after hashing\n", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "checksum mismatch"): + verify.verify_package(package) + + def test_private_paths_and_credentials_are_rejected(self) -> None: + for leaked in ( + "/Users/alice/project", + "/private/var/folders/zz/transient-build", + "github_pat_abcdefghijklmnopqrstuvwxyz", + ): + with self.subTest(leaked=leaked), tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + (package / "README.md").write_text(leaked + "\n", encoding="utf-8") + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, "found in package file"): + verify.verify_package(package) + + +class RuntimeContractTests(unittest.TestCase): + def _assert_json_mutation_rejected( + self, document_name: str, mutate, pattern: str | None = None + ) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / document_name + document = json.loads(path.read_text(encoding="utf-8")) + mutate(document) + write_json(path, document) + refresh_checksums(package) + expectation = ( + self.assertRaisesRegex(ValueError, pattern) + if pattern is not None + else self.assertRaises(ValueError) + ) + with expectation: + verify.verify_package(package) + + def test_runtime_identity_and_visual_only_authority_are_exact(self) -> None: + for key, value in ( + ("enemyKind", "other"), + ("combatEnabled", True), + ("encounterLevel", True), + ): + with self.subTest(key=key), tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.RUNTIME_MANIFEST + manifest = json.loads(path.read_text(encoding="utf-8")) + manifest[key] = value + write_json(path, manifest) + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, f"unexpected {key}"): + verify.verify_package(package) + + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.RUNTIME_MANIFEST + manifest = json.loads(path.read_text(encoding="utf-8")) + manifest["authorityBoundary"]["health"] = True + write_json(path, manifest) + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, "authorityBoundary"): + verify.verify_package(package) + + def test_runtime_manifest_rejects_unknown_fields_recursively(self) -> None: + cases = ( + ( + "runtime top level", + verify.RUNTIME_MANIFEST, + lambda value: value.__setitem__("worldAuthority", True), + ), + ( + "runtime LOD guidance", + verify.RUNTIME_MANIFEST, + lambda value: value["lodGuidance"].__setitem__("automaticCombat", True), + ), + ( + "runtime LOD record", + verify.RUNTIME_MANIFEST, + lambda value: value["lods"][0].__setitem__("spawnAuthority", True), + ), + ( + "runtime material contract", + verify.RUNTIME_MANIFEST, + lambda value: value["materialContract"].__setitem__("shaderCode", "remote"), + ), + ( + "runtime motion contract", + verify.RUNTIME_MANIFEST, + lambda value: value["motion"].__setitem__("attack", True), + ), + ( + "runtime selection guidance", + verify.RUNTIME_MANIFEST, + lambda value: value["selectionGuidance"].__setitem__("authoritative", True), + ), + ( + "asset top level", + verify.ASSET_MANIFEST, + lambda value: value.__setitem__("productionActive", True), + ), + ( + "asset design intent", + verify.ASSET_MANIFEST, + lambda value: value["designIntent"].__setitem__("combat", "enabled"), + ), + ( + "asset runtime contract", + verify.ASSET_MANIFEST, + lambda value: value["runtimeContracts"].__setitem__("network", "required"), + ), + ( + "asset Watcher record", + verify.ASSET_MANIFEST, + lambda value: value["watcher"].__setitem__("rewards", 100), + ), + ) + for label, document_name, mutate in cases: + with self.subTest(label=label): + self._assert_json_mutation_rejected( + document_name, mutate, "unexpected" + ) + + def test_runtime_coordinate_lod_motion_selection_and_state_are_exact(self) -> None: + cases = ( + ( + "authoring coordinate system", + lambda value: value.__setitem__("authoringCoordinateSystem", "left-handed"), + ), + ( + "runtime coordinate system", + lambda value: value.__setitem__("coordinateSystem", "left-handed"), + ), + ( + "front", + lambda value: value.__setitem__("frontFacing", "-Z"), + ), + ( + "pivot", + lambda value: value.__setitem__("pivot", "object origin"), + ), + ( + "meters per unit", + lambda value: value.__setitem__("metersPerUnit", 100.0), + ), + ( + "state", + lambda value: value.__setitem__("state", "actively-attacking"), + ), + ( + "LOD guidance", + lambda value: value["lodGuidance"]["suggestedDistancesMeters"].__setitem__( + "LOD0_HighThrough", -1 + ), + ), + ( + "motion", + lambda value: value["motion"].__setitem__( + "reducedMotion", "continuous" + ), + ), + ( + "selection", + lambda value: value["selectionGuidance"].__setitem__( + "suggestedPickCylinderRadiusMeters", -1.0 + ), + ), + ( + "material palette", + lambda value: value["materialContract"].__setitem__( + "palette", "Hegemony gold" + ), + ), + ) + for label, mutate in cases: + with self.subTest(label=label): + self._assert_json_mutation_rejected(verify.RUNTIME_MANIFEST, mutate) + + def test_asset_design_runtime_status_state_and_fingerprint_are_exact(self) -> None: + cases = ( + ( + "name", + lambda value: value.__setitem__("name", "Different asset"), + ), + ( + "design intent", + lambda value: value["designIntent"].__setitem__( + "camera", "first-person combat" + ), + ), + ( + "authoring runtime contract", + lambda value: value["runtimeContracts"].__setitem__( + "motion", "always attack" + ), + ), + ( + "status", + lambda value: value.__setitem__("status", "live-production"), + ), + ( + "Watcher state", + lambda value: value["watcher"].__setitem__( + "state", "actively-attacking" + ), + ), + ( + "source fingerprint", + lambda value: value.__setitem__( + "sourceSemanticFingerprintSha256", "a" * 64 + ), + ), + ) + for label, mutate in cases: + with self.subTest(label=label): + self._assert_json_mutation_rejected(verify.ASSET_MANIFEST, mutate) + + def test_declared_authoring_bounds_must_match_emitted_gltf_geometry(self) -> None: + def exclude_geometry(value: dict) -> None: + bounds = value["lods"][0]["boundsBlender"] + bounds["max"][0] -= 0.1 + bounds["size"][0] = bounds["max"][0] - bounds["min"][0] + + self._assert_json_mutation_rejected( + verify.RUNTIME_MANIFEST, exclude_geometry, "does not contain emitted glTF" + ) + + def loosen_bounds(value: dict) -> None: + bounds = value["lods"][0]["boundsBlender"] + bounds["min"][0] -= 0.2 + bounds["size"][0] = bounds["max"][0] - bounds["min"][0] + + self._assert_json_mutation_rejected( + verify.RUNTIME_MANIFEST, loosen_bounds, "too loose" + ) + + def test_lod_triangle_and_byte_counts_must_strictly_descend(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + oversized_balanced = make_semantic_glb( + "LOD1_Balanced", triangles_per_mesh=2 + ) + update_runtime_lod(package, "LOD1_Balanced", oversized_balanced) + with self.assertRaisesRegex(ValueError, "strictly descending"): + verify.verify_package(package) + + def test_manifest_metrics_are_recomputed_from_glb(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.RUNTIME_MANIFEST + manifest = json.loads(path.read_text(encoding="utf-8")) + manifest["lods"][0]["triangles"] += 1 + write_json(path, manifest) + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, "unexpected triangles"): + verify.verify_package(package) + + def test_qa_requires_exact_unique_58_of_58_check_contract(self) -> None: + for mutation in ("one-check", "duplicate-name"): + with self.subTest(mutation=mutation), tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.QA_REPORT + qa = json.loads(path.read_text(encoding="utf-8")) + if mutation == "one-check": + qa["checks"] = qa["checks"][:1] + qa["checksTotal"] = 1 + qa["checksPassed"] = 1 + else: + qa["checks"][1]["check"] = qa["checks"][0]["check"] + write_json(path, qa) + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, "exact 58 unique passed checks"): + verify.verify_package(package) + + def test_qa_rejects_unknown_claims_and_timestamp_drift(self) -> None: + for label, mutate in ( + ("claim", lambda report: report.__setitem__("productionReady", True)), + ( + "timestamp", + lambda report: report.__setitem__( + "generatedAt", "2099-01-01T00:00:00+00:00" + ), + ), + ): + with self.subTest(label=label), tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.QA_REPORT + report = json.loads(path.read_text(encoding="utf-8")) + mutate(report) + write_json(path, report) + refresh_checksums(package) + with self.assertRaises(ValueError): + verify.verify_package(package) + + def test_runtime_material_values_must_match_emitted_glbs(self) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + path = package / verify.RUNTIME_MANIFEST + manifest = json.loads(path.read_text(encoding="utf-8")) + ultraviolet = next( + material + for material in manifest["materialContract"]["materials"] + if material["name"] == "WK_Core_Ultraviolet" + ) + ultraviolet["emissiveStrength"] += 0.25 + write_json(path, manifest) + refresh_checksums(package) + with self.assertRaisesRegex(ValueError, "differs from LOD0_High emitted"): + verify.verify_package(package) + + +class PreviewStructureTests(unittest.TestCase): + def test_png_rejects_metadata_chunks_and_bad_crc(self) -> None: + for chunk_type in (b"eXIf", b"tEXt", b"zTXt", b"iTXt", b"tIME"): + with self.subTest(chunk_type=chunk_type): + payload = png_image(512, 512, ((chunk_type, b"metadata"),)) + with self.assertRaisesRegex(ValueError, "forbidden PNG metadata chunk"): + verify._png_dimensions(payload, "preview.png") + + corrupt = bytearray(png_image(512, 512)) + corrupt[29] ^= 0x01 + with self.assertRaisesRegex(ValueError, "CRC mismatch"): + verify._png_dimensions(bytes(corrupt), "preview.png") + + def test_png_rejects_custom_chunks_and_decompression_bombs(self) -> None: + custom = png_image(512, 512, ((b"vpAg", zlib.compress(b"private")),)) + with self.assertRaisesRegex(ValueError, "unexpected PNG ancillary"): + verify._png_dimensions(custom, "preview.png") + + width = height = 16 + ihdr = struct.pack(">IIBBBBB", width, height, 1, 0, 0, 0, 0) + expected = height * (1 + (width + 7) // 8) + bomb = b"".join( + ( + b"\x89PNG\r\n\x1a\n", + png_chunk(b"IHDR", ihdr), + png_chunk(b"IDAT", zlib.compress(b"\x00" * (expected + 1))), + png_chunk(b"IEND", b""), + ) + ) + with self.assertRaisesRegex(ValueError, "oversized PNG pixel stream"): + verify._png_dimensions(bomb, "bomb.png") + + palette_ihdr = struct.pack(">IIBBBBB", 8, 8, 1, 3, 0, 0, 0) + palette = b"".join( + ( + b"\x89PNG\r\n\x1a\n", + png_chunk(b"IHDR", palette_ihdr), + png_chunk(b"IDAT", zlib.compress((b"\x00\x00") * 8)), + png_chunk(b"IEND", b""), + ) + ) + with self.assertRaisesRegex(ValueError, "invalid PNG IHDR values"): + verify._png_dimensions(palette, "palette.png") + + def test_jpeg_rejects_comment_and_application_metadata(self) -> None: + clean = jpeg_frame(1920, 1080) + for marker in (0xFE, 0xE1, 0xE2, 0xED): + with self.subTest(marker=marker): + metadata = b"private metadata" + segment = ( + b"\xff" + + bytes((marker,)) + + struct.pack(">H", len(metadata) + 2) + + metadata + ) + # Insert after SOF to ensure the verifier scans past dimensions. + scan_offset = clean.index(b"\xff\xda") + payload = clean[:scan_offset] + segment + clean[scan_offset:] + with self.assertRaisesRegex(ValueError, "forbidden JPEG metadata marker"): + verify._jpeg_dimensions(payload, "preview.jpg") + + app15 = b"\xff\xef\x00\x09private" + payload = clean[:2] + app15 + clean[2:] + with self.assertRaisesRegex(ValueError, "APP15"): + verify._jpeg_dimensions(payload, "preview.jpg") + + malformed_jfif = clean.replace(b"JFIF\x00", b"JFXX\x00", 1) + with self.assertRaisesRegex(ValueError, "JFIF header"): + verify._jpeg_dimensions(malformed_jfif, "preview.jpg") + + def test_jpeg_requires_quantization_huffman_and_entropy_data(self) -> None: + clean = jpeg_frame(64, 64) + + def without_segment(payload: bytes, marker: bytes) -> bytes: + start = payload.index(marker) + length = struct.unpack_from(">H", payload, start + 2)[0] + return payload[:start] + payload[start + 2 + length :] + + without_quantization = without_segment(clean, b"\xff\xdb") + with self.assertRaisesRegex(ValueError, "frame components"): + verify._jpeg_dimensions(without_quantization, "preview.jpg") + + without_dc = without_segment(clean, b"\xff\xc4") + with self.assertRaisesRegex(ValueError, "baseline scan contract"): + verify._jpeg_dimensions(without_dc, "preview.jpg") + + scan = clean.index(b"\xff\xda") + scan_length = struct.unpack_from(">H", clean, scan + 2)[0] + entropy_start = scan + 2 + scan_length + no_entropy = clean[:entropy_start] + b"\xff\xd9" + with self.assertRaisesRegex(ValueError, "no entropy-coded data"): + verify._jpeg_dimensions(no_entropy, "preview.jpg") + + +class IntegrationProfileTrustTests(unittest.TestCase): + def test_self_consistent_profile_change_still_requires_pinned_digest(self) -> None: + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "integration-profile.json" + document = json.loads( + verify.INTEGRATION_PROFILE_PATH.read_text(encoding="utf-8") + ) + document["status"]["reviewOnly"] = False + document["contractDigest"]["sha256"] = "0" * 64 + zeroed = ( + json.dumps(document, indent=4, sort_keys=True, ensure_ascii=False) + "\n" + ).encode("utf-8") + document["contractDigest"]["sha256"] = hashlib.sha256(zeroed).hexdigest() + path.write_text( + json.dumps(document, indent=4, sort_keys=True, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + with self.assertRaisesRegex(ValueError, "production-pinned digest"): + verify.load_integration_semantic_contracts(path) + + @unittest.skipUnless(hasattr(os, "symlink"), "symlinks unavailable") + def test_profile_must_be_bounded_regular_non_symlink(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + target = root / "target.json" + target.write_text("{}\n", encoding="utf-8") + linked = root / "linked.json" + linked.symlink_to(target) + with self.assertRaisesRegex(ValueError, "regular non-symlink"): + verify.load_integration_semantic_contracts(linked) + + oversized = root / "oversized.json" + oversized.write_bytes(b" " * (verify.MAX_TEXT_BYTES + 1)) + with self.assertRaisesRegex(ValueError, "exceeds size limit"): + verify.load_integration_semantic_contracts(oversized) + + +class SemanticGlbContractTests(unittest.TestCase): + def _assert_mutation_rejected(self, mutate, pattern: str) -> None: + with tempfile.TemporaryDirectory() as directory: + package = make_package(Path(directory)) + mutate_runtime_glb(package, "LOD0_High", mutate) + with self.assertRaisesRegex(ValueError, pattern): + verify.verify_package(package) + + def test_part_node_rename_is_rejected(self) -> None: + self._assert_mutation_rejected( + lambda document: document["nodes"][0].__setitem__( + "name", "CoreWatcher_RenamedPart" + ), + "part node list", + ) + + def test_mesh_rename_is_rejected(self) -> None: + self._assert_mutation_rejected( + lambda document: document["meshes"][0].__setitem__( + "name", "CoreWatcher_Wrong_Mesh" + ), + "mesh name", + ) + + def test_missing_semantic_role_is_rejected(self) -> None: + self._assert_mutation_rejected( + lambda document: document["nodes"][0]["extras"].pop( + "warpkeep_semantic_role" + ), + "semantic role", + ) + + def test_material_reassignment_is_rejected(self) -> None: + self._assert_mutation_rejected( + lambda document: document["meshes"][0]["primitives"][0].__setitem__( + "material", 1 + ), + "material assignment", + ) + + def test_noncanonical_hierarchy_is_rejected(self) -> None: + def reorder_root_children(document: dict) -> None: + children = document["nodes"][-1]["children"] + children[0], children[1] = children[1], children[0] + + self._assert_mutation_rejected(reorder_root_children, "flat root-to-parts") + + def test_multiple_primitives_per_mesh_are_rejected(self) -> None: + def duplicate_primitive(document: dict) -> None: + primitives = document["meshes"][0]["primitives"] + primitives.append(copy.deepcopy(primitives[0])) + + self._assert_mutation_rejected(duplicate_primitive, "exactly one primitive") + + def test_root_extras_are_exact(self) -> None: + self._assert_mutation_rejected( + lambda document: document["nodes"][-1]["extras"].__setitem__( + "warpkeep_combat_enabled", True + ), + "root extras", + ) + + +class DeepGlbTests(unittest.TestCase): + def test_rejects_resource_expansion_and_claim_bearing_unknown_fields(self) -> None: + document, binary = glb_document(1) + document["accessors"][0]["count"] = verify.MAX_ACCESSOR_ELEMENTS + 1 + with self.assertRaisesRegex(ValueError, "accessor count exceeds resource limit"): + verify.inspect_glb(encode_glb(document, binary)) + + for label, mutate in ( + ("top", lambda value: value.__setitem__("combatAuthority", True)), + ( + "material", + lambda value: value["materials"][0].__setitem__("health", 100), + ), + ("scene", lambda value: value["scenes"][0].__setitem__("rewards", 10)), + ( + "accessor", + lambda value: value["accessors"][0].__setitem__("remoteUri", "x"), + ), + ): + with self.subTest(label=label): + document, binary = glb_document(1) + mutate(document) + with self.assertRaises(ValueError): + verify.inspect_glb(encode_glb(document, binary)) + + def test_rejects_transform_overflow_and_hierarchy_cycles(self) -> None: + document, binary = glb_document(1) + document["nodes"] = [ + {"children": [1], "scale": [1e308, 1e308, 1e308]}, + {"mesh": 0, "scale": [1e308, 1e308, 1e308]}, + ] + document["scenes"][0]["nodes"] = [0] + with self.assertRaisesRegex(ValueError, "transform matrix overflowed"): + verify.inspect_glb(encode_glb(document, binary)) + + document, binary = glb_document(1) + document["nodes"] = [ + {"mesh": 0, "children": [1]}, + {"children": [0]}, + ] + document["scenes"][0]["nodes"] = [0] + with self.assertRaisesRegex(ValueError, "cycle in node hierarchy"): + verify.inspect_glb(encode_glb(document, binary)) + + def test_rejects_external_uri_and_unsupported_extension(self) -> None: + document, binary = glb_document(1) + document["buffers"][0]["uri"] = "mesh.bin" + with self.assertRaisesRegex(ValueError, "URI"): + verify.inspect_glb(encode_glb(document, binary)) + + document, binary = glb_document(1) + document["extensionsUsed"].append("KHR_draco_mesh_compression") + with self.assertRaisesRegex(ValueError, "extension declaration"): + verify.inspect_glb(encode_glb(document, binary)) + + def test_rejects_wrong_material_contract(self) -> None: + document, binary = glb_document(1) + document["materials"][0]["name"] = "Borrowed_Hegemony_Material" + with self.assertRaisesRegex(ValueError, "material names"): + verify.inspect_glb(encode_glb(document, binary)) + + def test_rejects_out_of_range_and_degenerate_indices(self) -> None: + document, binary = glb_document(1) + index_offset = document["bufferViews"][2]["byteOffset"] + malformed = bytearray(binary) + struct.pack_into(" None: + document, binary = glb_document(1) + document["meshes"][0]["primitives"][0]["attributes"]["TEXCOORD_0"] = 1 + with self.assertRaisesRegex(ValueError, "exactly POSITION and NORMAL"): + verify.inspect_glb(encode_glb(document, binary)) + + def test_rejects_nonfinite_positions_and_accessor_overrun(self) -> None: + document, binary = glb_document(1) + malformed = bytearray(binary) + struct.pack_into(" None: + document, binary = glb_document(1) + malformed = bytearray(binary) + normal_offset = document["bufferViews"][1]["byteOffset"] + struct.pack_into(" None: + document, binary = glb_document(1) + document["nodes"] = [ + {"name": "CoreWatcher_Root", "children": [1], "translation": [0.1, 0.2, 0.3]}, + {"name": "CoreWatcher_Mesh", "mesh": 0, "translation": [0.4, 0.5, 0.6]}, + ] + document["scenes"][0]["nodes"] = [0] + metric = verify.inspect_glb(encode_glb(document, binary)) + for actual, expected in zip(metric.bounds_gltf_min, (0.5, 0.7, 0.9)): + self.assertAlmostEqual(actual, expected, places=6) + for actual, expected in zip(metric.bounds_gltf_max, (0.75, 1.2, 1.0)): + self.assertAlmostEqual(actual, expected, places=6) + + +if __name__ == "__main__": + unittest.main()