<!-- SAGA2 · part 57 -->
he proven patterns from native games, web games, and our research.
 * DO NOT MODIFY — these are the law.
 */
(function(){
'use strict';

var MASTER = {
  version: '1.0.0',
  sealed: '2026-10-02',
  by: 'Solid',

  /* === CHARACTER CONTROL === */
  character: {
    // Dual joysticks: left = body, right = camera. No toggles, no switches.
    joysticks: { left: 'body', right: 'camera' },
    // Body faces travel direction: yaw = atan2(dx, dz), damped at 0.15/frame
    facing: { method: 'atan2(dx,dz)', damping: 0.15 },
    // Movement modes
    modes: ['walk', 'fly', 'swim'],
    // Auto-swim: enter water → swim, exit → walk (Genshin rule)
    autoSwim: true,
    // Walk/run by joystick magnitude (0-1 analog)
    speedByMagnitude: true,
  },

  /* === CAMERA === */
  camera: {
    // Drag-to-look always live. Right joystick also controls camera.
    dragAlwaysLive: true,
    // Third-person follow
    type: 'third-person-follow',
  },

  /* === ANIMATION === */
  animation: {
    // State-gated: walk anim only in walk mode, swim only in swim
    stateGated: true,
    // Separate phase clocks per mode (never share)
    clocks: { walk: 'independent', swim: 'independent' },
    // Blend: amplitude lerp at 0.15/frame, no hard cuts
    blend: { method: 'amplitude-lerp', rate: 0.15 },
  },

  /* === BLOCK MECHANICS === */
  blocks: {
    // Placement: raycast → hit cell + face normal → adjacent cell
    placement: 'hitCell + faceNormal',
    // Targeting: DDA voxel traversal (Amanatides & Woo)
    targeting: 'DDA-voxel-raycast',
    // Rotation: auto-face from camera yaw at placement (Minecraft rule)
    rotation: 'auto-face-from-camera-yaw',
    // Correction: 90° spin button
    spinCorrection: 'PI/2',
    // Grid snapping: integer cells
    snapping: 'integer-grid',
    // Validity: reject if occupied, reject if overlapping player
    validity: ['not-occupied', 'not-overlapping-player'],
  },

  /* === PICKUP === */
  pickup: {
    // Break → item entity → 0.5s delay → walk within 1.4 → auto-collect
    delay: 0.5,           // seconds (anti-snatch)
    radius: 1.4,          // blocks
    mergeDistance: 0.75,  // same-type merge
    despawn: 300,         // seconds (5 min)
    sound: 'pickup',
  },

  /* === INVENTORY === */
  inventory: {
    slots: 36,
    hotbar: 9,            // tappable slots
    stackMax: { block: 64, special: 16, tool: 1 },
    creative: 'infinite',
  },

  /* === DEPLOYING === */
  deploying: {
    // Tap hotbar → tap world → ghost preview → tap commits if valid
    flow: ['select', 'aim', 'ghost-preview', 'commit'],
    ghost: { valid: '0x00ff00', invalid: '0xff0000', opacity: 0.4 },
    // Invalid: red ghost, no consume, no placement
    invalidHandling: 'red-ghost-no-consume',
    // Pick-block: tap existing block → load type into active slot
    pickBlock: true,
  },

  /* === UNDO/REDO === */
  undo: {
    pattern: 'Command-Pattern',  // {execute, undo} per command, never faked
    depth: 200,                   // edits (web standard)
  },

  /* === TOUCH === */
  touch: {
    // Gesture disambiguation
    gestures: { tap: 'place', hold: 'mine', drag: 'look' },
    holdThreshold: 500,    // ms for long-press
    slop: 10,              // px movement before drag
    // Hygiene
    touchAction: 'none',
    perPointerTracking: true,  // by identifier
    uiStopPropagation: true,   // UI touches never leak to canvas
  },

  /* === UI === */
  ui: {
    // Circular icon buttons. Never pill switches. Never ON/OFF text.
    buttons: 'circular-icon-only',
    forbidden: ['pill-switches', 'on-off-text', 'toggle-switches'],
    // Hotbar: tappable, gold frame on active
    hotbar: { selectable: 'tap', activeIndicator: 'gold-frame' },
    // King's law: icons only, no types, no keys, no numbers
    inputLaw: 'icons-only',
  },

  /* === SOUND === */
  sound: {
    engine: 'Web-Audio-API-synthesized',
    files: 'none',
    maxDuration: 0.3,  // seconds — all short
    effects: {
      place:  { f0: 800,  f1: 1600, dur: 0.12, type: 'square',   vol: 0.12 },
      pickup: { f0: 600,  f1: 1200, dur: 0.10, type: 'sine',     vol: 0.15 },
      brk:    { f0: 400,  f1: 100,  dur: 0.15, type: 'sawtooth', vol: 0.10 },
      click:  { f0: 1000, f1: 1000, dur: 0.05, type: 'square',   vol: 0.08 },
      error:  { f0: 200,  f1: 150,  dur: 0.20, type: 'sawtooth', vol: 0.10 },
      swim:   { f0: 300,  f1: 600,  dur: 0.18, type: 'sine',     vol: 0.08 },
      jump:   { f0: 400,  f1: 800,  dur: 0.12, type: 'triangle', vol: 0.12 },
      land:   { f0: 300,  f1: 150,  dur: 0.10, type: 'sine',     vol: 0.10 },
      spin:   { f0: 500,  f1: 2000, dur: 0.15, type: 'sawtooth', vol: 0.08 },
      undo:   { f0: 1200, f1: 600,  dur: 0.12, type: 'square',   vol: 0.10 },
    },
  },

  /* === PHYSICS CONSTANTS === */
  physics: {
    minimum: 0.13,  // the King's eyes — nothing smaller
  },
};

// Freeze it — unchangeable
Object.freeze(MASTER);
Object.freeze(MASTER.character);
Object.freeze(MAST