<!-- SAGA-SOLID · part 18 -->
numbers elsewhere
   ============================================================ */
var LIMITS = {
  MIN_COMPONENT_DIM: 0.13,        // matches window.applyMinimum
  MAX_COMPONENT_DIM: 50,          // max single dimension per component
  MAX_BLUEPRINT_DIM: 100,         // max bounding box per axis
  MAX_COMPONENTS_PER_BLUEPRINT: 50,
  MAX_COMPLEXITY_BUDGET: 50000,   // triangle estimate budget
  MAX_LIGHTS_PER_BLUEPRINT: 8,
  MAX_NAME_LENGTH: 120,
  MAX_DESCRIPTION_LENGTH: 1000,
  MAX_STRING_FIELD: 256,          // generic string cap
  MAX_TAGS_PER_BLUEPRINT: 20,
  MAX_TAG_LENGTH: 40,
};

/* ============================================================
   V1 COMPONENT REGISTRY — 7 primitive types
   Each: id, allowed dimension ranges, triangle estimate factor,
   whether it emits light, whether it is structural
   ============================================================ */
var COMPONENT_REGISTRY = {
  box: {
    type: 'box',
    description: 'Solid rectangular prism',
    triangleEstimate: 12,
    emitsLight: false,
    structural: true,
  },
  cylinder: {
    type: 'cylinder',
    description: 'Solid cylinder (radius from width/depth)',
    triangleEstimate: 64,
    emitsLight: false,
    structural: true,
  },
  platform: {
    type: 'platform',
    description: 'Flat horizontal slab',
    triangleEstimate: 12,
    emitsLight: false,
    structural: true,
  },
  wall: {
    type: 'wall',
    description: 'Vertical rectangular panel',
    triangleEstimate: 12,
    emitsLight: false,
    structural: true,
  },
  floor: {
    type: 'floor',
    description: 'Horizontal ground surface panel',
    triangleEstimate: 12,
    emitsLight: false,
    structural: true,
  },
  column: {
    type: 'column',
    description: 'Vertical support pillar',
    triangleEstimate: 32,
    emitsLight: false,
    structural: true,
  },
  light: {
    type: 'light',
    description: 'Point light emitter (no shadow in V1)',
    triangleEstimate: 16,
    emitsLight: true,
    structural: false,
  },
};

/* ============================================================
   PERSISTENT ID GENERATION
   Format: playa-<kind>-<base36 timestamp>-<random 6 chars>
   Kinds: bp (blueprint), obj (manufactured object),
          comp (component), txn (transaction), intent
   ============================================================ */
var _idCounter = 0;
function generateId(kind) {
  var ts = Date.now().toString(36);
  var rnd = Math.random().toString(36).substring(2, 8);
  _idCounter = (_idCounter + 1) % 1296; // base36 2-digit rollover
  var ctr = _idCounter.toString(36).padStart(2, '0');
  return 'playa-' + kind + '-' + ts + '-' + rnd + ctr;
}

/* ============================================================
   SECURITY: pure-data validation helpers
   ============================================================ */

/* Dangerous keys that indicate prototype pollution or code injection */
var FORBIDDEN_KEYS = ['__proto__', 'constructor', 'prototype'];

function hasForbiddenKeys(obj, path) {
  path = path || 'root';
  if(obj === null || typeof obj !== 'object') return null;
  // Detect prototype tampering (e.g. obj['__proto__'] = {...})
  var proto = Object.getPrototypeOf(obj);
  if(proto !== Object.prototype && proto !== Array.prototype && proto !== null) {
    return {path: path, reason: 'prototype tampering detected'};
  }
  var keys = Object.keys(obj);
  for(var i = 0; i < keys.length; i++) {
    var k = keys[i];
    if(FORBIDDEN_KEYS.indexOf(k) !== -1) {
      return {path: path + '.' + k, reason: 'forbidden key: ' + k};
    }
    var nested = hasForbiddenKeys(obj[k], path + '.' + k);
    if(nested) return nested;
  }
  return null;
}

/* Reject anything that looks like executable content */
function isExecutableValue(v) {
  if(typeof v === 'function') return true;
  if(typeof v !== 'string') return false;
  var s = v.trim().toLowerCase();
  // Function constructors, eval, script tags, URLs
  if(s.indexOf('function(') === 0 || s.indexOf('function ') === 0) return true;
  if(s.indexOf('=>') !== -1 && s.length < 200) return true; // arrow fn heuristic
  if(s.indexOf('eval(') !== -1) return true;
  if(s.indexOf('<script') !== -1) return true;
  if(s.indexOf('javascript:') === 0) return true;
  if(s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return true;
  if(s.indexOf('data:') === 0) return true;
  return false;
}

function isValidNumber(n) {
  return typeof n === 'number' && isFinite(n) && !isNaN(n);
}

function isValidString(s, maxLen) {
  return typeof s === 'string' && s.length <= (maxLen || LIMITS.MAX_STRING_FIELD);
}

/* ============================================================
   BLUEPRINT SCHEMA
   ============================================================ */

function createBlueprint(spec) {
  /* spec: {name, creator, description?, components[], tags?[]} */
  var errors = [];

  if(!spec || typeof spec !== 'object') {
    return {ok: false, errors: [{field: 'spec', reason: 'spe