2 Commits
Author SHA1 Message Date
John Smith 08a49e2f55 fix: remediate audit of studio pipeline, 3D culling, and instancing
16-reviewer audit of ae815f489 (103 findings) remediated in six groups.

1. Lazy THREE init (runtimeobject.ts): scratch Box3/Matrix4/Euler allocated on
   first use, so a 2D-only export (never loads three.js) boots.

2. Culling correctness: computeAABB uses |size|/2 half-extents and places the box
   at the true rotation pivot C' = P + R*(C-P); custom-object children union
   conservative boxes; the frustum is rebuilt in the boxes' space (right-multiply
   _threeGroup.matrixWorld) with a numeric cache incl. camera-Z; hide/cull route
   through setCullingVisible/applyRenderVisibility (no detached-carrier writes);
   custom-object children and nested tile maps no longer cull with a wrong window.

3. Studio pipeline wedge (Utils.js + Studio/*): lock release over all calls,
   working marks only editor calls, spawn failures reach the model, plan merge on
   objects (JSON.parse bug), one persistent LoopGuard with real call_id +
   re-arming + recordSuccess, RequestWriteGate self-retry + supersede-rejects,
   UseStudioRuntime fire-and-forget gate write + recomputed plan + sent check,
   FinalizeSubAgents terminal-'error' + executed-but-unsent + turn-cap + enriched
   report, send-returns-boolean, BYOK cache write-through, suspension cascade,
   turn-cap dispatch, stand-alone cannot spawn.

4. Instancing: useInstancing now serializes (C++); the pool grows without null.add
   and collapses deleted slots; the instance matrix is normalized
   (carrier * correction) and rewritten on rotation/hide; animated/skinned/morph/
   multi-mesh models rejected; per-slot shadows; layer moves re-acquire.

5. BYOK role enforcement: studioRoleId persisted and re-applied every turn (tools +
   prompt); dispatch filters tools outside the role; readOnly roles skip edit
   approval.

6. Warnings + Info: mutating-tool classification, hasOwnProperty role guards,
   cache-message merge, spawn arg caps, per-batch GDD note, fan-out cap, plan
   dependsOn-omission + caps + duplicate-id + fail-closed + trimmed ids, tilemap
   rotated/mirror bail + window margin + null-map guard, pool dispose wired to
   unloadScene, schema description required, plus doc/comment corrections.

Verified: engine builds clean; boot + computeAABB (13/13) + Model3DInstancePool
(9/9) harnesses pass; Jest green for src/AiGeneration + src/AI (175), CustomAIClient
(27, incl. the critical second-turn role regression), PlanStore (25),
FinalizeSubAgents (21), LoopGuard (18), RequestWriteGate (11), Roles (9).
2026-09-22 15:31:25 -04:00
John Smith ae815f489b feat(ai): local multi-agent game studio + engine culling and instancing
Answers the two questions the fork asked, then acts on both.

Assessment (AI_GAME_STUDIO_ASSESSMENT.md): a literal Anno-1800-scale 3D
city builder is not achievable on this engine - no instancing, no 3D
culling, Android is a Cordova WebView around the same HTML5 runtime, and a
15 MB per-resource cloud-build ceiling. A 2D isometric city builder of
Anno-1602/1701 scope is reachable today. The hosted backend already is a
multi-agent company; the BYOK path can reach none of it (no plan tool, no
sub-agent tool, no role prompts, no local spawner, no loop guard).

Studio (phases 2-5, 9-11), all under newIDE/app/src/AiGeneration/Studio/:
* Roles.js - manager/designer/developer/tester: prompts, exact tool
  subsets, read-only invariant, turn caps.
* PlanStore.js - create_or_update_plan is real under BYOK and returns the
  shape getLatestActivePlan/OrchestratorPlan already read; the dispatcher
  merges a new plan over the existing one so a re-plan cannot strip a field
  another writer set.
* SpawnSubAgents.js + customCreateSubAgentAiRequest - spawn_agent (the BYOK
  tool schema goes 29 -> 39 tools) and a real child AiRequest carrying the
  role's prompt and tool subset.
* FinalizeSubAgents.js + UseStudioRuntime.js - a finished child's report is
  written onto its parent's function_call_output and the plan task flips.
* RequestWriteGate.js - one gate owns writes to an AiRequest, with named
  block reasons and latest-wins per-request serialization.
* LoopGuard.js - a local circuit breaker (repeated identical calls, error
  storms, turn count) with a steer -> constrain ladder. Ported from
  chaitanyagiri/munder-difflin (MIT) src/main/breaker.ts, incl. its #377
  whole-arguments sha256 key; no MD source is vendored.
* One in-flight model turn per request id, and the two in-place mutators
  made whole-replace so a concurrent turn cannot discard them.

Engine (phases 6-8):
* 3D culling: gdjs.Object3DCulling shared AABB maths, a per-layer frustum
  cache, and a 3D branch in both _updateObjectsPreRender implementations.
  Capability-checked, because CustomRuntimeObject3D does not extend
  RuntimeObject3D.
* Instancing: opt-in Model3D "Use instancing" property + Model3DInstancePool,
  one InstancedMesh per (resource, material, layer) for single non-skinned
  meshes; every other model silently keeps the clone path.
* TileMap culling generalised to TileMap (not just SimpleTileMap) and to
  tilemaps nested in custom objects, with bounds clamped to the map.

Two bugs found by running the loop end-to-end rather than by tests: the
instance pool was page-global (handing out slots on a discarded scene's
mesh), and buildSubAgentReport read only `output_text` while the local path
emits `text`, so every local sub-agent reported "(no report)".
2026-09-22 11:19:22 -04:00