<!-- SAGA-STAGING · part 21 -->
 — add to list
    validated.push({
      componentId: compId,
      type: c.type,
      dimensions: {w: w, h: h, d: d},
      position: {x: pos.x, y: pos.y, z: pos.z},
      rotation: {x: rot.x, y: rot.y, z: rot.z},
      material: material,
      color: color,
    });

    complexity += reg.triangleEstimate;
    if(reg.emitsLight) lightCount++;
  }

  return {
    errors: errors,
    components: validated,
    complexity: complexity,
    lightCount: lightCount,
  };
}

/* ============================================================
   BLUEPRINT-LOCAL BOUNDS CALCULATION
   Computes axis-aligned bounding box of all components
   in blueprint-local coordinates
   ============================================================ */
function calculateBounds(components) {
  if(!components || components.length === 0) {
    return {minX: 0, maxX: 0, minY: 0, maxY: 0, minZ: 0, maxZ: 0, sizeX: 0, sizeY: 0, sizeZ: 0};
  }

  var minX = Infinity, maxX = -Infinity;
  var minY = Infinity, maxY = -Infinity;
  var minZ = Infinity, maxZ = -Infinity;

  for(var i = 0; i < components.length; i++) {
    var c = components[i];
    var hw = c.dimensions.w / 2, hh = c.dimensions.h / 2, hd = c.dimensions.d / 2;
    // Conservative: use full half-extents (ignores rotation for simplicity in V1)
    // V2 should do proper OBB calculation
    var cx = c.position.x, cy = c.position.y, cz = c.position.z;
    minX = Math.min(minX, cx - hw); maxX = Math.max(maxX, cx + hw);
    minY = Math.min(minY, cy - hh); maxY = Math.max(maxY, cy + hh);
    minZ = Math.min(minZ, cz - hd); maxZ = Math.max(maxZ, cz + hd);
  }

  return {
    minX: minX, maxX: maxX,
    minY: minY, maxY: maxY,
    minZ: minZ, maxZ: maxZ,
    sizeX: maxX - minX, sizeY: maxY - minY, sizeZ: maxZ - minZ,
  };
}

/* ============================================================
   MANUFACTURED OBJECT SCHEMA (data definition only)
   One persistent entity containing components.
   No instantiation in Stage 1.
   ============================================================ */
function defineObjectSchema() {
  return {
    description: 'A manufactured Playa world entity. One persistent ID per creation, containing N components.',
    fields: {
      persistentId: 'string, playa-obj-..., unique, immutable',
      blueprintId: 'string, reference to source blueprint',
      blueprintVersion: 'integer',
      name: 'string, human-readable',
      creator: 'object {type: human|ai, identity, agentId?}',
      createdAt: 'timestamp ms',
      transactionId: 'string, playa-txn-...',
      placement: 'object {x, y, z, rotationY} world coordinates',
      parcelId: 'string, parcel where placed',
      components: 'array of {componentId, type, dimensions, position, rotation, material, color, runtimeRef?}',
      bounds: 'object, world-space AABB calculated at manufacture',
      collisionRegistered: 'boolean',
      lifecycleState: 'string: active | rolled-back | deleted | archived',
      updatedAt: 'timestamp ms',
    },
  };
}

/* ============================================================
   PARCEL SCHEMA (data definition only)
   Stage 1 defines schema. No physical parcel approved.
   ============================================================ */
function defineParcelSchema() {
  return {
    description: 'A bounded buildable region. Stage 1 schema only; no parcel approved for physical use.',
    fields: {
      parcelId: 'string, unique',
      name: 'string',
      centerX: 'number, world X of parcel center',
      centerZ: 'number, world Z of parcel center',
      halfSize: 'number, half-extent in world units (square parcel)',
      maxObjects: 'integer',
      allowedCreators: 'array of creator type strings',
      surveyed: 'boolean, true only after terrain/support/occupancy survey',
      approved: 'boolean, true only after King approval',
      createdAt: 'timestamp ms',
    },
    candidate: {
      note: 'Coordinates 2000,2000 are CANDIDATE ONLY. Not surveyed. Not approved.',
      centerX: 2000,
      centerZ: 2000,
      halfSize: 100,
    },
  };
}

/* ============================================================
   TRANSACTION SCHEMA (data definition only)
   ============================================================ */
function defineTransactionSchema() {
  return {
    description: 'Atomic manufacturing operation. Either fully commits or fully rolls back.',
    fields: {
      transactionId: 'string, playa-txn-..., unique',
      blueprintId: 'string',
      intentId: 'string, playa-intent-...',
      status: 'string: pending | committed | rolled-back | failed',
      objectId: 'string, the manufactured object persistent ID (one per transaction in V1)',
      componentIds: 'array of strings',
      startedAt: 'timestamp ms',
      completedAt: 'timestamp ms or null',
      error: 'string or null',
    },
    states: ['pending', 'committed', 'rolled-back', 'failed'],
  };
}

/* ============================================================
  