<!-- SAGA-STAGING · part 107 -->
&& !window._spPromptShown){
      window._spPromptShown = true;
      if(typeof say === 'function') say('✦ Soul Print\'s Tower — type spradio for music, spchat to speak');
      // Register proximity commands if not already
      if(typeof CODES !== 'undefined'){
        if(!CODES['spradio']) CODES['spradio'] = {cat:'Soul Print', desc:'Open radio', fn:function(){ window.SPRadio.show(); }};
        if(!CODES['spchat']) CODES['spchat'] = {cat:'Soul Print', desc:'Talk to Soul Print', fn:function(){ window.SPChat.openPanel(); }};
      }
    } else if(dist >= 40){
      window._spPromptShown = false;
    }
  }, 2000);
})();


/* ===== UNIVERSAL CONTROLLER — map adapter + race course. King's order 2026-10-07 ===== */
/* Smallest proof: 3 checkpoints, human + AI, validated timing, same physics. */
(function(){
  if(window._raceBuilt) return;
  
  // MAP ADAPTER — staging implements the universal interface
  window.MapAdapter = {
    name: 'staging-new-world',
    // Terrain height at (x,z) — queries Foundation
    getHeight: function(x, z){
      if(window.WF && window.WF.groundY){
        return window.WF.groundY(x, z);
      }
      return 0;
    },
    // Spawn positions
    getSpawn: function(actorId){
      // Human spawns at origin, AI spawns offset
      if(actorId === 'ai-1') return {x: 5, y: 0, z: 5};
      return {x: 0, y: 0, z: 0};
    },
    // World boundaries
    getBounds: function(){
      return {minX: -200, maxX: 200, minZ: -200, maxZ: 200};
    },
    // Checkpoints for the race course
    getCheckpoints: function(){
      return [
        {id: 'cp1', x: 0, z: -80, radius: 10, name: 'Hill Base'},      // At the hill
        {id: 'cp2', x: 100, z: 0, radius: 10, name: 'Plateau'},        // At the plateau
        {id: 'cp3', x: -100, z: 0, radius: 10, name: 'Landmark'}       // At the landmark
      ];
    }
  };
  
  // RACE MANAGER — tracks human and AI through checkpoints
  window.RaceManager = {
    racers: {},
    active: false,
    startTime: 0,
    init: function(){
      var cps = window.MapAdapter.getCheckpoints();
      // Build visible checkpoint rings
      cps.forEach(function(cp, idx){
        var ringGeo = new THREE.TorusGeometry(cp.radius, 0.5, 8, 32);
        var ringMat = new THREE.MeshStandardMaterial({
          color: idx === 0 ? 0x00ff00 : 0xffff00,
          emissive: idx === 0 ? 0x00aa00 : 0xaaaa00,
          emissiveIntensity: 0.5
        });
        var ring = new THREE.Mesh(ringGeo, ringMat);
        ring.rotation.x = Math.PI/2;
        ring.position.set(cp.x, 2, cp.z);
        scene.add(ring);
        cp.mesh = ring;
        if(window.WORLD_REGISTRY) window.WORLD_REGISTRY.register(ring, 'race-checkpoint');
      });
      window._raceBuilt = true;
      if(typeof say === 'function') say('Race course ready: 3 checkpoints');
    },
    startRace: function(racerId){
      if(!this.racers[racerId]){
        this.racers[racerId] = {checkpoints: [], startTime: Date.now(), finished: false};
      } else {
        this.racers[racerId] = {checkpoints: [], startTime: Date.now(), finished: false};
      }
      this.active = true;
      if(typeof say === 'function') say('Race started for ' + racerId);
    },
    checkPosition: function(racerId, x, z){
      if(!this.active || !this.racers[racerId] || this.racers[racerId].finished) return;
      var racer = this.racers[racerId];
      var cps = window.MapAdapter.getCheckpoints();
      var nextIdx = racer.checkpoints.length;
      if(nextIdx >= cps.length) return;
      
      var cp = cps[nextIdx];
      var dx = x - cp.x, dz = z - cp.z;
      var dist = Math.sqrt(dx*dx + dz*dz);
      
      if(dist < cp.radius){
        var elapsed = (Date.now() - racer.startTime) / 1000;
        racer.checkpoints.push({id: cp.id, time: elapsed});
        if(cp.mesh) cp.mesh.material.color.setHex(0x00ff00);
        if(typeof say === 'function') say(racerId + ' hit ' + cp.name + ' at ' + elapsed.toFixed(1) + 's');
        
        if(racer.checkpoints.length >= cps.length){
          racer.finished = true;
          racer.totalTime = elapsed;
          if(typeof say === 'function') say('🏁 ' + racerId + ' FINISHED in ' + elapsed.toFixed(1) + 's');
        }
      }
    },
    getResults: function(){
      return this.racers;
    }
  };
  
  // AI ACTOR — simple waypoint follower using same physics
  window.AIActor = {
    id: 'ai-1',
    pos: {x: 5, y: 0, z: 5},
    target: null,
    speed: 8, // units per second (same as human run speed)
    mesh: null,
    active: false,
    init: function(){
      // Create visible AI avatar (blue sphere)
      var geo = new THREE.SphereGeometry(2, 16, 16);
      var mat = new THREE.MeshStandardMaterial({color: 0x0088ff, roughness: 0.5});
      this.mesh = new THREE.Mesh(geo, mat);
      var spawn = window.MapAdapter.getSpawn(this.id);
      this.pos.x = spawn.x; this.pos.z = spawn.z;
      this.mesh.position.set(this.pos.x, 2, this.pos.z);
      scene.add(this.mesh);
      