[
  {
    "schemaVersion": 1,
    "title": "adapters/PBDRagdollAgentAdapter",
    "path": "agi/reference/adapters/PBDRagdollAgentAdapter.md",
    "source": "agi/adapters/PBDRagdollAgentAdapter.js",
    "import": "/agi/adapters/PBDRagdollAgentAdapter.js",
    "sourceHash": "c617ddeda76aa47cbee3e7af4dc72dd8ddde7cc5fce76f545db3c1e36fd3f9d3",
    "summary": "",
    "exports": [
      {
        "name": "PBDRagdollAgentAdapter",
        "kind": "class",
        "signature": "class PBDRagdollAgentAdapter",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "brain/ExperienceBuffer",
    "path": "agi/reference/brain/ExperienceBuffer.md",
    "source": "agi/brain/ExperienceBuffer.js",
    "import": "/agi/brain/ExperienceBuffer.js",
    "sourceHash": "63538bf2e35b05177c5527d73d67997316caab4d98969bb6746ef0a2d32ba4e1",
    "summary": "",
    "exports": [
      {
        "name": "ExperienceBuffer",
        "kind": "class",
        "signature": "class ExperienceBuffer",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "brain/NetworkArchitecture",
    "path": "agi/reference/brain/NetworkArchitecture.md",
    "source": "agi/brain/NetworkArchitecture.js",
    "import": "/agi/brain/NetworkArchitecture.js",
    "sourceHash": "96d127e43ea3c4d4b717561078bdeecc9efb341e393b25c8214a9da5ad4dd5f4",
    "summary": "Layer class - represents a single neural network layer",
    "exports": [
      {
        "name": "Layer",
        "kind": "class",
        "signature": "class Layer",
        "summary": "Layer class - represents a single neural network layer"
      },
      {
        "name": "NETWORK_ARCHITECTURES",
        "kind": "constant",
        "signature": "NETWORK_ARCHITECTURES",
        "summary": "Network Architecture Definitions Layer configurations and hyperparameters for Policy and Value networks"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "brain/PolicyNetwork",
    "path": "agi/reference/brain/PolicyNetwork.md",
    "source": "agi/brain/PolicyNetwork.js",
    "import": "/agi/brain/PolicyNetwork.js",
    "sourceHash": "6afeb61b0f881e76c5952c95655bc26cfbcd02670d7dab75859857bb7fa9ec98",
    "summary": "",
    "exports": [
      {
        "name": "PolicyNetwork",
        "kind": "class",
        "signature": "class PolicyNetwork",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "brain/ValueNetwork",
    "path": "agi/reference/brain/ValueNetwork.md",
    "source": "agi/brain/ValueNetwork.js",
    "import": "/agi/brain/ValueNetwork.js",
    "sourceHash": "a8768cfbc58ba7acf9656ff451b78b4fac2bf49cdbce4b45e5b6a9dff5b2593c",
    "summary": "",
    "exports": [
      {
        "name": "ValueNetwork",
        "kind": "class",
        "signature": "class ValueNetwork",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "brain/trainers/PPOTrainer",
    "path": "agi/reference/brain/trainers/PPOTrainer.md",
    "source": "agi/brain/trainers/PPOTrainer.js",
    "import": "/agi/brain/trainers/PPOTrainer.js",
    "sourceHash": "9ef5331dbd0e566011cba2dcdaeb672138eb9040c461152d6aab61a741a6b88a",
    "summary": "",
    "exports": [
      {
        "name": "PPOTrainer",
        "kind": "class",
        "signature": "class PPOTrainer",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "config/curriculum",
    "path": "agi/reference/config/curriculum.md",
    "source": "agi/config/curriculum.js",
    "import": "/agi/config/curriculum.js",
    "sourceHash": "faac029b5a078f894addf4f1810a1e04b028aaa5e37fcaf4da8c70586c1e2893",
    "summary": "Curriculum Learning Stages Progressive training stages from standing to full locomotion",
    "exports": [
      {
        "name": "CURRICULUM_STAGES",
        "kind": "constant",
        "signature": "CURRICULUM_STAGES",
        "summary": "Curriculum Learning Stages Progressive training stages from standing to full locomotion"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "config/hyperparameters",
    "path": "agi/reference/config/hyperparameters.md",
    "source": "agi/config/hyperparameters.js",
    "import": "/agi/config/hyperparameters.js",
    "sourceHash": "a5a8c1d2731a9c858ed889d8bb72b8cbef9838f3cf2c4f68d00cbf685485ead7",
    "summary": "Neural Network Hyperparameters Configuration for Policy and Value networks, training parameters",
    "exports": [
      {
        "name": "HYPERPARAMETERS",
        "kind": "constant",
        "signature": "HYPERPARAMETERS",
        "summary": "Neural Network Hyperparameters Configuration for Policy and Value networks, training parameters"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "config/rewards",
    "path": "agi/reference/config/rewards.md",
    "source": "agi/config/rewards.js",
    "import": "/agi/config/rewards.js",
    "sourceHash": "a54ceb15b1a4b9edba1c462a25c6cf35d0ea99546e3729b6a2bd4704c4eef292",
    "summary": "Compute reward for current state",
    "exports": [
      {
        "name": "computeReward",
        "kind": "function",
        "signature": "computeReward(state, config = REWARD_WEIGHTS.standing)",
        "summary": "Compute reward for current state"
      },
      {
        "name": "REWARD_WEIGHTS",
        "kind": "constant",
        "signature": "REWARD_WEIGHTS",
        "summary": "Reward Function Weights Configurable reward components for different training objectives"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/CurriculumManager",
    "path": "agi/reference/core/CurriculumManager.md",
    "source": "agi/core/CurriculumManager.js",
    "import": "/agi/core/CurriculumManager.js",
    "sourceHash": "4b2fdc7d123b94cdd1d734d3998d70fc2bfa60043fc6c9003ec5d63455d11179",
    "summary": "",
    "exports": [
      {
        "name": "CurriculumManager",
        "kind": "class",
        "signature": "class CurriculumManager",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/MotionMatchingTeacher",
    "path": "agi/reference/core/MotionMatchingTeacher.md",
    "source": "agi/core/MotionMatchingTeacher.js",
    "import": "/agi/core/MotionMatchingTeacher.js",
    "sourceHash": "8067ac4e7eb42fd195398395e3cd8b35098562e5c8cd79a6a0e1f049ce22fbf7",
    "summary": "",
    "exports": [
      {
        "name": "MotionMatchingTeacher",
        "kind": "class",
        "signature": "class MotionMatchingTeacher extends MotionMatcher",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/MotorController",
    "path": "agi/reference/core/MotorController.md",
    "source": "agi/core/MotorController.js",
    "import": "/agi/core/MotorController.js",
    "sourceHash": "e92518a41940bd87db2e88b406599fd661679e73cf9d561f45cab80e30dabd03",
    "summary": "Motor Controller Converts neural network actions into physics impulses",
    "exports": [
      {
        "name": "MotorController",
        "kind": "class",
        "signature": "class MotorController",
        "summary": "Motor Controller Converts neural network actions into physics impulses"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/ObservationBuilder",
    "path": "agi/reference/core/ObservationBuilder.md",
    "source": "agi/core/ObservationBuilder.js",
    "import": "/agi/core/ObservationBuilder.js",
    "sourceHash": "74c5e77974cdfdec7ae331f82796deafff336adfcae527282cb31a90cf99ab81",
    "summary": "Observation Builder Converts ragdoll sensor data into neural network input",
    "exports": [
      {
        "name": "ObservationBuilder",
        "kind": "class",
        "signature": "class ObservationBuilder",
        "summary": "Observation Builder Converts ragdoll sensor data into neural network input"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/RagdollController",
    "path": "agi/reference/core/RagdollController.md",
    "source": "agi/core/RagdollController.js",
    "import": "/agi/core/RagdollController.js",
    "sourceHash": "c248723f6e650dcff7a470393e6c11d4dac0bec1e37d27bc03ba9c8d0c4096f8",
    "summary": "",
    "exports": [
      {
        "name": "RagdollController",
        "kind": "class",
        "signature": "class RagdollController",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "core/RewardFunction",
    "path": "agi/reference/core/RewardFunction.md",
    "source": "agi/core/RewardFunction.js",
    "import": "/agi/core/RewardFunction.js",
    "sourceHash": "8f94baf0346ff8e13cb398786fef280535c407b60b10db85aaa1806581f575b6",
    "summary": "",
    "exports": [
      {
        "name": "RewardFunction",
        "kind": "class",
        "signature": "class RewardFunction",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "index",
    "path": "agi/reference/index.md",
    "source": "agi/index.js",
    "import": "/agi/index.js",
    "sourceHash": "f20d34de48bbf9d6566a9ffe9b10f489ef732fb3740aea98512226867c7ca502",
    "summary": "",
    "exports": [
      {
        "name": "AGI_CORE_FULL",
        "kind": "re-export",
        "signature": "AGI_CORE_FULL",
        "summary": ""
      },
      {
        "name": "AGI_CORE_VERSION",
        "kind": "re-export",
        "signature": "AGI_CORE_VERSION",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_CHANNEL_ID",
        "kind": "re-export",
        "signature": "AGI_TRAINING_CHANNEL_ID",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_CONTRACT",
        "kind": "re-export",
        "signature": "AGI_TRAINING_CONTRACT",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_ROUTE_ID",
        "kind": "re-export",
        "signature": "AGI_TRAINING_ROUTE_ID",
        "summary": ""
      },
      {
        "name": "AgiTrainingStateChannel",
        "kind": "re-export",
        "signature": "AgiTrainingStateChannel",
        "summary": ""
      },
      {
        "name": "BUILD_TAG",
        "kind": "re-export",
        "signature": "BUILD_TAG",
        "summary": ""
      },
      {
        "name": "BrainVisualizer",
        "kind": "re-export",
        "signature": "BrainVisualizer",
        "summary": ""
      },
      {
        "name": "ComputeGraph",
        "kind": "re-export",
        "signature": "ComputeGraph",
        "summary": ""
      },
      {
        "name": "CurriculumManager",
        "kind": "re-export",
        "signature": "CurriculumManager",
        "summary": ""
      },
      {
        "name": "DebugVisualizer",
        "kind": "re-export",
        "signature": "DebugVisualizer",
        "summary": ""
      },
      {
        "name": "ExperienceBuffer",
        "kind": "re-export",
        "signature": "ExperienceBuffer",
        "summary": ""
      },
      {
        "name": "GradientContext",
        "kind": "re-export",
        "signature": "GradientContext",
        "summary": ""
      },
      {
        "name": "GradientTape",
        "kind": "re-export",
        "signature": "GradientTape",
        "summary": ""
      },
      {
        "name": "GroundPlane",
        "kind": "re-export",
        "signature": "GroundPlane",
        "summary": ""
      },
      {
        "name": "InjectionSystem",
        "kind": "re-export",
        "signature": "InjectionSystem",
        "summary": ""
      },
      {
        "name": "Layer",
        "kind": "re-export",
        "signature": "Layer",
        "summary": ""
      },
      {
        "name": "LoadingProgress",
        "kind": "re-export",
        "signature": "LoadingProgress",
        "summary": ""
      },
      {
        "name": "MetricsDisplay",
        "kind": "re-export",
        "signature": "MetricsDisplay",
        "summary": ""
      },
      {
        "name": "ModelLoader",
        "kind": "re-export",
        "signature": "ModelLoader",
        "summary": ""
      },
      {
        "name": "MotionMatchingTeacher",
        "kind": "re-export",
        "signature": "MotionMatchingTeacher",
        "summary": ""
      },
      {
        "name": "MotorController",
        "kind": "re-export",
        "signature": "MotorController",
        "summary": ""
      },
      {
        "name": "NETWORK_ARCHITECTURES",
        "kind": "re-export",
        "signature": "NETWORK_ARCHITECTURES",
        "summary": ""
      },
      {
        "name": "NetworkArchitecture",
        "kind": "re-export",
        "signature": "NetworkArchitecture",
        "summary": ""
      },
      {
        "name": "ObservationBuilder",
        "kind": "re-export",
        "signature": "ObservationBuilder",
        "summary": ""
      },
      {
        "name": "PBDRagdollAgentAdapter",
        "kind": "re-export",
        "signature": "PBDRagdollAgentAdapter",
        "summary": ""
      },
      {
        "name": "PPOTrainer",
        "kind": "re-export",
        "signature": "PPOTrainer",
        "summary": ""
      },
      {
        "name": "ParasiteRig",
        "kind": "re-export",
        "signature": "ParasiteRig",
        "summary": ""
      },
      {
        "name": "PolicyNetwork",
        "kind": "re-export",
        "signature": "PolicyNetwork",
        "summary": ""
      },
      {
        "name": "RagdollController",
        "kind": "re-export",
        "signature": "RagdollController",
        "summary": ""
      },
      {
        "name": "RewardFunction",
        "kind": "re-export",
        "signature": "RewardFunction",
        "summary": ""
      },
      {
        "name": "SceneRenderer",
        "kind": "re-export",
        "signature": "SceneRenderer",
        "summary": ""
      },
      {
        "name": "StudioApp",
        "kind": "re-export",
        "signature": "StudioApp",
        "summary": ""
      },
      {
        "name": "TensorCache",
        "kind": "re-export",
        "signature": "TensorCache",
        "summary": ""
      },
      {
        "name": "TentacleRenderer",
        "kind": "re-export",
        "signature": "TentacleRenderer",
        "summary": ""
      },
      {
        "name": "TrackingCamera",
        "kind": "re-export",
        "signature": "TrackingCamera",
        "summary": ""
      },
      {
        "name": "TrainingScene",
        "kind": "re-export",
        "signature": "TrainingScene",
        "summary": ""
      },
      {
        "name": "TrainingUI",
        "kind": "re-export",
        "signature": "TrainingUI",
        "summary": ""
      },
      {
        "name": "ValueNetwork",
        "kind": "re-export",
        "signature": "ValueNetwork",
        "summary": ""
      },
      {
        "name": "createAgiTrainingStateChannel",
        "kind": "re-export",
        "signature": "createAgiTrainingStateChannel",
        "summary": ""
      },
      {
        "name": "loadingProgress",
        "kind": "re-export",
        "signature": "loadingProgress",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/DeviceProfiler",
    "path": "agi/reference/llm/DeviceProfiler.md",
    "source": "agi/llm/DeviceProfiler.js",
    "import": "/agi/llm/DeviceProfiler.js",
    "sourceHash": "31c08824be41f7aa80654a5cbfa109250c7ba8d998213935abe7dc176e279945",
    "summary": "DeviceProfiler.js - WebGPU capability and LLM feasibility profiler. This module is intentionally browser-native and dependency-free. It can use the kernel's shared GPU device when available, or probe navigator.gpu directly for standalone AGI tests.",
    "exports": [
      {
        "name": "AIDeviceProfiler",
        "kind": "class",
        "signature": "class AIDeviceProfiler",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/GGUFParser",
    "path": "agi/reference/llm/formats/GGUFParser.md",
    "source": "agi/llm/formats/GGUFParser.js",
    "import": "/agi/llm/formats/GGUFParser.js",
    "sourceHash": "a7c01840a7315dcc9cbd906b3edaf8c843ade73612d4a63ce023571d1e49c921",
    "summary": "",
    "exports": [
      {
        "name": "GGUFParser",
        "kind": "class",
        "signature": "class GGUFParser",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/GGUFTensorReader",
    "path": "agi/reference/llm/formats/GGUFTensorReader.md",
    "source": "agi/llm/formats/GGUFTensorReader.js",
    "import": "/agi/llm/formats/GGUFTensorReader.js",
    "sourceHash": "bd77c6687c617369f002717fd7bef7429eeebd0a8ba27eca501e9c39e18c258a",
    "summary": "",
    "exports": [
      {
        "name": "GGUFTensorReader",
        "kind": "class",
        "signature": "class GGUFTensorReader",
        "summary": ""
      },
      {
        "name": "bf16ToF32",
        "kind": "function",
        "signature": "bf16ToF32(value)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/LLMPersistenceContracts",
    "path": "agi/reference/llm/formats/LLMPersistenceContracts.md",
    "source": "agi/llm/formats/LLMPersistenceContracts.js",
    "import": "/agi/llm/formats/LLMPersistenceContracts.js",
    "sourceHash": "6bb1547664180c24ce26d1760ea42eb59574058ad0e089e9452d39492a128b03",
    "summary": "Accept a legacy bare conversation and return the canonical durable v1 envelope.",
    "exports": [
      {
        "name": "validateConversationId",
        "kind": "function",
        "signature": "validateConversationId(value)",
        "summary": ""
      },
      {
        "name": "validateModelId",
        "kind": "function",
        "signature": "validateModelId(value)",
        "summary": ""
      },
      {
        "name": "prepareModelRegistry",
        "kind": "function",
        "signature": "prepareModelRegistry(input)",
        "summary": ""
      },
      {
        "name": "prepareModelPackage",
        "kind": "function",
        "signature": "prepareModelPackage(input)",
        "summary": ""
      },
      {
        "name": "prepareConversation",
        "kind": "function",
        "signature": "prepareConversation(input, expectedId = null)",
        "summary": "Accept a legacy bare conversation and return the canonical durable v1 envelope."
      },
      {
        "name": "MODEL_REGISTRY_SCHEMA",
        "kind": "constant",
        "signature": "MODEL_REGISTRY_SCHEMA",
        "summary": ""
      },
      {
        "name": "MODEL_PACKAGE_SCHEMA",
        "kind": "constant",
        "signature": "MODEL_PACKAGE_SCHEMA",
        "summary": ""
      },
      {
        "name": "CONVERSATION_SCHEMA",
        "kind": "constant",
        "signature": "CONVERSATION_SCHEMA",
        "summary": ""
      },
      {
        "name": "LLM_SCHEMA_VERSION",
        "kind": "constant",
        "signature": "LLM_SCHEMA_VERSION",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/ModelPackageReader",
    "path": "agi/reference/llm/formats/ModelPackageReader.md",
    "source": "agi/llm/formats/ModelPackageReader.js",
    "import": "/agi/llm/formats/ModelPackageReader.js",
    "sourceHash": "0c36a0a464be2eace294b50d704e66ebf4035dbf64daa8dc84be8601cdd6069d",
    "summary": "",
    "exports": [
      {
        "name": "ModelPackageReader",
        "kind": "class",
        "signature": "class ModelPackageReader",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/ModelPackageWriter",
    "path": "agi/reference/llm/formats/ModelPackageWriter.md",
    "source": "agi/llm/formats/ModelPackageWriter.js",
    "import": "/agi/llm/formats/ModelPackageWriter.js",
    "sourceHash": "3e4a12517cf5cb5eeee7b685113e49e0dd850fe8344488e78f2a844c9d8e8a3e",
    "summary": "",
    "exports": [
      {
        "name": "ModelPackageWriter",
        "kind": "class",
        "signature": "class ModelPackageWriter",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/Quantization",
    "path": "agi/reference/llm/formats/Quantization.md",
    "source": "agi/llm/formats/Quantization.js",
    "import": "/agi/llm/formats/Quantization.js",
    "sourceHash": "f7a2023a142bedad1e3eef00d91e5578221d7250d8645963db0272be33fc4386",
    "summary": "",
    "exports": [
      {
        "name": "quantTypeName",
        "kind": "function",
        "signature": "quantTypeName(type)",
        "summary": ""
      },
      {
        "name": "ggufFileTypeName",
        "kind": "function",
        "signature": "ggufFileTypeName(type)",
        "summary": ""
      },
      {
        "name": "inferQuantization",
        "kind": "function",
        "signature": "inferQuantization(tensors = [], metadata = {})",
        "summary": ""
      },
      {
        "name": "normalizeQuantization",
        "kind": "function",
        "signature": "normalizeQuantization(value)",
        "summary": ""
      },
      {
        "name": "isSupportedMetadataQuant",
        "kind": "function",
        "signature": "isSupportedMetadataQuant(value)",
        "summary": ""
      },
      {
        "name": "quantBlockInfo",
        "kind": "function",
        "signature": "quantBlockInfo(type)",
        "summary": ""
      },
      {
        "name": "tensorElementCount",
        "kind": "function",
        "signature": "tensorElementCount(dims = [])",
        "summary": ""
      },
      {
        "name": "tensorByteSize",
        "kind": "function",
        "signature": "tensorByteSize(type, dims = [])",
        "summary": ""
      },
      {
        "name": "GGML_QUANT_TYPES",
        "kind": "constant",
        "signature": "GGML_QUANT_TYPES",
        "summary": ""
      },
      {
        "name": "SUPPORTED_METADATA_QUANTS",
        "kind": "constant",
        "signature": "SUPPORTED_METADATA_QUANTS",
        "summary": ""
      },
      {
        "name": "GGUF_FILE_TYPES",
        "kind": "constant",
        "signature": "GGUF_FILE_TYPES",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/formats/TensorShardIndex",
    "path": "agi/reference/llm/formats/TensorShardIndex.md",
    "source": "agi/llm/formats/TensorShardIndex.js",
    "import": "/agi/llm/formats/TensorShardIndex.js",
    "sourceHash": "bf83fd0a109ee8b96bae74d3b80a2ce1c49f01b70e7f7db4503d27b638dda4c2",
    "summary": "Accept legacy bare indexes and return a canonical, independently cloned v1 document.",
    "exports": [
      {
        "name": "UnsupportedTensorShardIndexVersionError",
        "kind": "class",
        "signature": "class UnsupportedTensorShardIndexVersionError extends Error",
        "summary": ""
      },
      {
        "name": "TensorShardIndex",
        "kind": "class",
        "signature": "class TensorShardIndex",
        "summary": ""
      },
      {
        "name": "prepareTensorShardIndexDocument",
        "kind": "function",
        "signature": "prepareTensorShardIndexDocument(input)",
        "summary": "Accept legacy bare indexes and return a canonical, independently cloned v1 document."
      },
      {
        "name": "TENSOR_SHARD_INDEX_SCHEMA",
        "kind": "constant",
        "signature": "TENSOR_SHARD_INDEX_SCHEMA",
        "summary": ""
      },
      {
        "name": "TENSOR_SHARD_INDEX_SCHEMA_VERSION",
        "kind": "constant",
        "signature": "TENSOR_SHARD_INDEX_SCHEMA_VERSION",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/index",
    "path": "agi/reference/llm/index.md",
    "source": "agi/llm/index.js",
    "import": "/agi/llm/index.js",
    "sourceHash": "7a63122268e40a7d5b4c4af344681240db64d95bf9122ad16bc9845ba0d9a007",
    "summary": "",
    "exports": [
      {
        "name": "AIDeviceProfiler",
        "kind": "re-export",
        "signature": "AIDeviceProfiler",
        "summary": ""
      },
      {
        "name": "CONVERSATION_SCHEMA",
        "kind": "re-export",
        "signature": "CONVERSATION_SCHEMA",
        "summary": ""
      },
      {
        "name": "CpuTensorRunner",
        "kind": "re-export",
        "signature": "CpuTensorRunner",
        "summary": ""
      },
      {
        "name": "GGUFParser",
        "kind": "re-export",
        "signature": "GGUFParser",
        "summary": ""
      },
      {
        "name": "GGUFTensorReader",
        "kind": "re-export",
        "signature": "GGUFTensorReader",
        "summary": ""
      },
      {
        "name": "Gemma4GraphRunner",
        "kind": "re-export",
        "signature": "Gemma4GraphRunner",
        "summary": ""
      },
      {
        "name": "GenerationLoop",
        "kind": "re-export",
        "signature": "GenerationLoop",
        "summary": ""
      },
      {
        "name": "GpuPrimitiveRunner",
        "kind": "re-export",
        "signature": "GpuPrimitiveRunner",
        "summary": ""
      },
      {
        "name": "GraphCompiler",
        "kind": "re-export",
        "signature": "GraphCompiler",
        "summary": ""
      },
      {
        "name": "InferenceEngine",
        "kind": "re-export",
        "signature": "InferenceEngine",
        "summary": ""
      },
      {
        "name": "KVCache",
        "kind": "re-export",
        "signature": "KVCache",
        "summary": ""
      },
      {
        "name": "K_QUANT_LAYOUTS",
        "kind": "re-export",
        "signature": "K_QUANT_LAYOUTS",
        "summary": ""
      },
      {
        "name": "KernelRegistry",
        "kind": "re-export",
        "signature": "KernelRegistry",
        "summary": ""
      },
      {
        "name": "LLMRuntime",
        "kind": "re-export",
        "signature": "LLMRuntime",
        "summary": ""
      },
      {
        "name": "LLMSession",
        "kind": "re-export",
        "signature": "LLMSession",
        "summary": ""
      },
      {
        "name": "LLM_SCHEMA_VERSION",
        "kind": "re-export",
        "signature": "LLM_SCHEMA_VERSION",
        "summary": ""
      },
      {
        "name": "LocalGGUFExecutor",
        "kind": "re-export",
        "signature": "LocalGGUFExecutor",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_QUARANTINE_SCHEMA",
        "kind": "re-export",
        "signature": "MODEL_HANDLE_QUARANTINE_SCHEMA",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_RECORD_SCHEMA",
        "kind": "re-export",
        "signature": "MODEL_HANDLE_RECORD_SCHEMA",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_RECORD_VERSION",
        "kind": "re-export",
        "signature": "MODEL_HANDLE_RECORD_VERSION",
        "summary": ""
      },
      {
        "name": "MODEL_PACKAGE_SCHEMA",
        "kind": "re-export",
        "signature": "MODEL_PACKAGE_SCHEMA",
        "summary": ""
      },
      {
        "name": "MODEL_REGISTRY_SCHEMA",
        "kind": "re-export",
        "signature": "MODEL_REGISTRY_SCHEMA",
        "summary": ""
      },
      {
        "name": "ModelCache",
        "kind": "re-export",
        "signature": "ModelCache",
        "summary": ""
      },
      {
        "name": "ModelHandleStore",
        "kind": "re-export",
        "signature": "ModelHandleStore",
        "summary": ""
      },
      {
        "name": "ModelHandleStoreError",
        "kind": "re-export",
        "signature": "ModelHandleStoreError",
        "summary": ""
      },
      {
        "name": "ModelPackageReader",
        "kind": "re-export",
        "signature": "ModelPackageReader",
        "summary": ""
      },
      {
        "name": "ModelPackageWriter",
        "kind": "re-export",
        "signature": "ModelPackageWriter",
        "summary": ""
      },
      {
        "name": "ModelRegistry",
        "kind": "re-export",
        "signature": "ModelRegistry",
        "summary": ""
      },
      {
        "name": "OPFSStore",
        "kind": "re-export",
        "signature": "OPFSStore",
        "summary": ""
      },
      {
        "name": "PagedAttention",
        "kind": "re-export",
        "signature": "PagedAttention",
        "summary": ""
      },
      {
        "name": "PrefixBlockCache",
        "kind": "re-export",
        "signature": "PrefixBlockCache",
        "summary": ""
      },
      {
        "name": "PromptTemplate",
        "kind": "re-export",
        "signature": "PromptTemplate",
        "summary": ""
      },
      {
        "name": "Q8_0_LAYOUT",
        "kind": "re-export",
        "signature": "Q8_0_LAYOUT",
        "summary": ""
      },
      {
        "name": "QuantizedMatVecRunner",
        "kind": "re-export",
        "signature": "QuantizedMatVecRunner",
        "summary": ""
      },
      {
        "name": "Sampler",
        "kind": "re-export",
        "signature": "Sampler",
        "summary": ""
      },
      {
        "name": "StaticMemoryPlanner",
        "kind": "re-export",
        "signature": "StaticMemoryPlanner",
        "summary": ""
      },
      {
        "name": "TENSOR_SHARD_INDEX_SCHEMA",
        "kind": "re-export",
        "signature": "TENSOR_SHARD_INDEX_SCHEMA",
        "summary": ""
      },
      {
        "name": "TENSOR_SHARD_INDEX_SCHEMA_VERSION",
        "kind": "re-export",
        "signature": "TENSOR_SHARD_INDEX_SCHEMA_VERSION",
        "summary": ""
      },
      {
        "name": "TensorShardIndex",
        "kind": "re-export",
        "signature": "TensorShardIndex",
        "summary": ""
      },
      {
        "name": "Tokenizer",
        "kind": "re-export",
        "signature": "Tokenizer",
        "summary": ""
      },
      {
        "name": "ToolBridge",
        "kind": "re-export",
        "signature": "ToolBridge",
        "summary": ""
      },
      {
        "name": "TransformerExecutor",
        "kind": "re-export",
        "signature": "TransformerExecutor",
        "summary": ""
      },
      {
        "name": "UnsupportedTensorShardIndexVersionError",
        "kind": "re-export",
        "signature": "UnsupportedTensorShardIndexVersionError",
        "summary": ""
      },
      {
        "name": "VFSConversationStore",
        "kind": "re-export",
        "signature": "VFSConversationStore",
        "summary": ""
      },
      {
        "name": "WeightCache",
        "kind": "re-export",
        "signature": "WeightCache",
        "summary": ""
      },
      {
        "name": "assertShaderCompiled",
        "kind": "re-export",
        "signature": "assertShaderCompiled",
        "summary": ""
      },
      {
        "name": "checksumBytes",
        "kind": "re-export",
        "signature": "checksumBytes",
        "summary": ""
      },
      {
        "name": "checksumString",
        "kind": "re-export",
        "signature": "checksumString",
        "summary": ""
      },
      {
        "name": "createComputePipelineChecked",
        "kind": "re-export",
        "signature": "createComputePipelineChecked",
        "summary": ""
      },
      {
        "name": "kernelNameForQuant",
        "kind": "re-export",
        "signature": "kernelNameForQuant",
        "summary": ""
      },
      {
        "name": "prepareConversation",
        "kind": "re-export",
        "signature": "prepareConversation",
        "summary": ""
      },
      {
        "name": "prepareLastModelRecord",
        "kind": "re-export",
        "signature": "prepareLastModelRecord",
        "summary": ""
      },
      {
        "name": "prepareModelHandleRecord",
        "kind": "re-export",
        "signature": "prepareModelHandleRecord",
        "summary": ""
      },
      {
        "name": "prepareModelPackage",
        "kind": "re-export",
        "signature": "prepareModelPackage",
        "summary": ""
      },
      {
        "name": "prepareModelRegistry",
        "kind": "re-export",
        "signature": "prepareModelRegistry",
        "summary": ""
      },
      {
        "name": "prepareTensorShardIndexDocument",
        "kind": "re-export",
        "signature": "prepareTensorShardIndexDocument",
        "summary": ""
      },
      {
        "name": "rowStrideForQuant",
        "kind": "re-export",
        "signature": "rowStrideForQuant",
        "summary": ""
      },
      {
        "name": "shaderQ4K",
        "kind": "re-export",
        "signature": "shaderQ4K",
        "summary": ""
      },
      {
        "name": "shaderQ5K",
        "kind": "re-export",
        "signature": "shaderQ5K",
        "summary": ""
      },
      {
        "name": "shaderQ6K",
        "kind": "re-export",
        "signature": "shaderQ6K",
        "summary": ""
      },
      {
        "name": "shaderQ80",
        "kind": "re-export",
        "signature": "shaderQ80",
        "summary": ""
      },
      {
        "name": "validateConversationId",
        "kind": "re-export",
        "signature": "validateConversationId",
        "summary": ""
      },
      {
        "name": "validateModelId",
        "kind": "re-export",
        "signature": "validateModelId",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/CacheGovernor",
    "path": "agi/reference/llm/inference/CacheGovernor.md",
    "source": "agi/llm/inference/CacheGovernor.js",
    "import": "/agi/llm/inference/CacheGovernor.js",
    "sourceHash": "678bf1b0b84834d9b88952f07e3e69eb481d83f331d7701fbe8b510945f596c0",
    "summary": "",
    "exports": [
      {
        "name": "CacheGovernor",
        "kind": "class",
        "signature": "class CacheGovernor",
        "summary": ""
      },
      {
        "name": "planAdaptivePreload",
        "kind": "function",
        "signature": "planAdaptivePreload(tensors = [], options = {})",
        "summary": ""
      },
      {
        "name": "adaptiveWeightPriority",
        "kind": "function",
        "signature": "adaptiveWeightPriority(tensor)",
        "summary": ""
      },
      {
        "name": "adaptiveEvictionScore",
        "kind": "function",
        "signature": "adaptiveEvictionScore(name, entry = {}, options = {})",
        "summary": ""
      },
      {
        "name": "weightFamily",
        "kind": "function",
        "signature": "weightFamily(name)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/CpuTensorRunner",
    "path": "agi/reference/llm/inference/CpuTensorRunner.md",
    "source": "agi/llm/inference/CpuTensorRunner.js",
    "import": "/agi/llm/inference/CpuTensorRunner.js",
    "sourceHash": "c2588dc9c5ee2b947451104f28ee92dff1e1f17363ed84f85985ca5cf0fc7166",
    "summary": "",
    "exports": [
      {
        "name": "CpuTensorRunner",
        "kind": "class",
        "signature": "class CpuTensorRunner",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/Gemma4GraphRunner",
    "path": "agi/reference/llm/inference/Gemma4GraphRunner.md",
    "source": "agi/llm/inference/Gemma4GraphRunner.js",
    "import": "/agi/llm/inference/Gemma4GraphRunner.js",
    "sourceHash": "b06865d59a227b489cf458884419f550c193fcd4f348c822ea648895665e2a51",
    "summary": "",
    "exports": [
      {
        "name": "Gemma4GraphRunner",
        "kind": "class",
        "signature": "class Gemma4GraphRunner",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/GenerationLoop",
    "path": "agi/reference/llm/inference/GenerationLoop.md",
    "source": "agi/llm/inference/GenerationLoop.js",
    "import": "/agi/llm/inference/GenerationLoop.js",
    "sourceHash": "05147a76cb527903174acb5343064fe163d06c7a1c0dfccbc0b2b3baef52aeb7",
    "summary": "",
    "exports": [
      {
        "name": "GenerationLoop",
        "kind": "class",
        "signature": "class GenerationLoop",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/GpuPrimitiveRunner",
    "path": "agi/reference/llm/inference/GpuPrimitiveRunner.md",
    "source": "agi/llm/inference/GpuPrimitiveRunner.js",
    "import": "/agi/llm/inference/GpuPrimitiveRunner.js",
    "sourceHash": "afb6ff6ca17b455d5d8111a205bce1064ade992a1c9179bd2cb23a9c15c84c70",
    "summary": "",
    "exports": [
      {
        "name": "GpuPrimitiveRunner",
        "kind": "class",
        "signature": "class GpuPrimitiveRunner",
        "summary": ""
      },
      {
        "name": "blockedTokenKey",
        "kind": "function",
        "signature": "blockedTokenKey(ids, vocabSize)",
        "summary": ""
      },
      {
        "name": "mapAsyncWithTimeout",
        "kind": "function",
        "signature": "mapAsyncWithTimeout(buffer, mode, timeoutMs, message)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/GpuRuntimeResources",
    "path": "agi/reference/llm/inference/GpuRuntimeResources.md",
    "source": "agi/llm/inference/GpuRuntimeResources.js",
    "import": "/agi/llm/inference/GpuRuntimeResources.js",
    "sourceHash": "bcf6b65ecef401e35378b047bf1085f9e9e7152a1258c249d1034a42bb911f79",
    "summary": "",
    "exports": [
      {
        "name": "GpuParameterArena",
        "kind": "class",
        "signature": "class GpuParameterArena",
        "summary": ""
      },
      {
        "name": "GpuBindGroupCache",
        "kind": "class",
        "signature": "class GpuBindGroupCache",
        "summary": ""
      },
      {
        "name": "uniformBinding",
        "kind": "function",
        "signature": "uniformBinding(allocation)",
        "summary": ""
      },
      {
        "name": "isArenaAllocation",
        "kind": "function",
        "signature": "isArenaAllocation(value)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/GraphCompiler",
    "path": "agi/reference/llm/inference/GraphCompiler.md",
    "source": "agi/llm/inference/GraphCompiler.js",
    "import": "/agi/llm/inference/GraphCompiler.js",
    "sourceHash": "30fb4ea82efd7f95c5fb61c28ca221010ed258a9d482cc7b5091a4c63b6c605b",
    "summary": "",
    "exports": [
      {
        "name": "GraphCompiler",
        "kind": "class",
        "signature": "class GraphCompiler",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/InferenceEngine",
    "path": "agi/reference/llm/inference/InferenceEngine.md",
    "source": "agi/llm/inference/InferenceEngine.js",
    "import": "/agi/llm/inference/InferenceEngine.js",
    "sourceHash": "8398db540595e51abbd5798548e3e7c3943cc41ca607eb35d68f31f838a2d132",
    "summary": "",
    "exports": [
      {
        "name": "InferenceEngine",
        "kind": "class",
        "signature": "class InferenceEngine",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/KVCache",
    "path": "agi/reference/llm/inference/KVCache.md",
    "source": "agi/llm/inference/KVCache.js",
    "import": "/agi/llm/inference/KVCache.js",
    "sourceHash": "d9c20edbeb25e42bd6fb81b302286919a2a1aeb217a7be1d1f2d9e71cb4bf385",
    "summary": "",
    "exports": [
      {
        "name": "KVCache",
        "kind": "class",
        "signature": "class KVCache",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/LocalGGUFExecutor",
    "path": "agi/reference/llm/inference/LocalGGUFExecutor.md",
    "source": "agi/llm/inference/LocalGGUFExecutor.js",
    "import": "/agi/llm/inference/LocalGGUFExecutor.js",
    "sourceHash": "615988a623002f6b6869c0e6551a9e56efdc8c54722ac5215fc60493544c5711",
    "summary": "",
    "exports": [
      {
        "name": "LocalGGUFExecutor",
        "kind": "class",
        "signature": "class LocalGGUFExecutor",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/PagedAttention",
    "path": "agi/reference/llm/inference/PagedAttention.md",
    "source": "agi/llm/inference/PagedAttention.js",
    "import": "/agi/llm/inference/PagedAttention.js",
    "sourceHash": "c941566582c2757e53b4260fdd676a70c898dc183570958ddc69fb3cf546e48b",
    "summary": "",
    "exports": [
      {
        "name": "PagedAttention",
        "kind": "class",
        "signature": "class PagedAttention",
        "summary": ""
      },
      {
        "name": "PrefixBlockCache",
        "kind": "class",
        "signature": "class PrefixBlockCache",
        "summary": ""
      },
      {
        "name": "blockHash",
        "kind": "function",
        "signature": "blockHash(namespace, prefixHash, blockTokens)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/QuantizedMatVecRunner",
    "path": "agi/reference/llm/inference/QuantizedMatVecRunner.md",
    "source": "agi/llm/inference/QuantizedMatVecRunner.js",
    "import": "/agi/llm/inference/QuantizedMatVecRunner.js",
    "sourceHash": "dc3596344dda9371cbfe8608dd4ebcfd96438f40595e2e36039a6ccff475c04d",
    "summary": "",
    "exports": [
      {
        "name": "QuantizedMatVecRunner",
        "kind": "class",
        "signature": "class QuantizedMatVecRunner",
        "summary": ""
      },
      {
        "name": "kernelNameForQuant",
        "kind": "function",
        "signature": "kernelNameForQuant(quantization)",
        "summary": ""
      },
      {
        "name": "batchedKernelNameForQuant",
        "kind": "function",
        "signature": "batchedKernelNameForQuant(quantization)",
        "summary": ""
      },
      {
        "name": "rowStrideForQuant",
        "kind": "function",
        "signature": "rowStrideForQuant(quantization, cols)",
        "summary": ""
      },
      {
        "name": "createKQuantParityProbeData",
        "kind": "function",
        "signature": "createKQuantParityProbeData(quantization, { rows = SUBGROUP_PROBE_ROWS } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/StaticMemoryPlanner",
    "path": "agi/reference/llm/inference/StaticMemoryPlanner.md",
    "source": "agi/llm/inference/StaticMemoryPlanner.js",
    "import": "/agi/llm/inference/StaticMemoryPlanner.js",
    "sourceHash": "263245e37abfd8cce2c8de11df753bff164b998624a21f7ca96fd5a415c1cf72",
    "summary": "",
    "exports": [
      {
        "name": "StaticMemoryPlanner",
        "kind": "class",
        "signature": "class StaticMemoryPlanner",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/TransformerExecutor",
    "path": "agi/reference/llm/inference/TransformerExecutor.md",
    "source": "agi/llm/inference/TransformerExecutor.js",
    "import": "/agi/llm/inference/TransformerExecutor.js",
    "sourceHash": "89f26b7091ee7dfa3ee3c7d3ef5f5af81315f19348762f3e7ab355433008539f",
    "summary": "",
    "exports": [
      {
        "name": "TransformerExecutor",
        "kind": "class",
        "signature": "class TransformerExecutor",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/inference/WeightCache",
    "path": "agi/reference/llm/inference/WeightCache.md",
    "source": "agi/llm/inference/WeightCache.js",
    "import": "/agi/llm/inference/WeightCache.js",
    "sourceHash": "196c9d86c78264fefad15369fcfa04bab453c289a4c4fbc3c67e6e6a9c28b5fc",
    "summary": "",
    "exports": [
      {
        "name": "WeightCache",
        "kind": "class",
        "signature": "class WeightCache",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/KernelRegistry",
    "path": "agi/reference/llm/kernels/KernelRegistry.md",
    "source": "agi/llm/kernels/KernelRegistry.js",
    "import": "/agi/llm/kernels/KernelRegistry.js",
    "sourceHash": "9a5e33335548532e5b6c2cb2513c0086b3cd3f125e336e175b61d8056cd991d0",
    "summary": "",
    "exports": [
      {
        "name": "KernelRegistry",
        "kind": "class",
        "signature": "class KernelRegistry",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/PipelineUtils",
    "path": "agi/reference/llm/kernels/PipelineUtils.md",
    "source": "agi/llm/kernels/PipelineUtils.js",
    "import": "/agi/llm/kernels/PipelineUtils.js",
    "sourceHash": "867a34da5e41e5f69215f113d0d1a0490cf4af3b332dcef251f3fc0d7c71c535",
    "summary": "",
    "exports": [
      {
        "name": "createComputePipelineChecked",
        "kind": "function",
        "signature": "async createComputePipelineChecked(device, module, { label, name = label, entryPoint = 'main' } = {})",
        "summary": ""
      },
      {
        "name": "assertShaderCompiled",
        "kind": "function",
        "signature": "async assertShaderCompiled(module, name = 'shader')",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/attention_decode",
    "path": "agi/reference/llm/kernels/shaders/attention_decode.md",
    "source": "agi/llm/kernels/shaders/attention_decode.js",
    "import": "/agi/llm/kernels/shaders/attention_decode.js",
    "sourceHash": "0be9494f3d3038420e5d1ef789b7101d019a2f21c1b32bf7d5c8581665d2280f",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/attention_decode_legacy",
    "path": "agi/reference/llm/kernels/shaders/attention_decode_legacy.md",
    "source": "agi/llm/kernels/shaders/attention_decode_legacy.js",
    "import": "/agi/llm/kernels/shaders/attention_decode_legacy.js",
    "sourceHash": "6fd4106a4e62961fe6b2444ca36a061e23487641f8ebdc9082215f704b29f602",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/attention_prefill_legacy",
    "path": "agi/reference/llm/kernels/shaders/attention_prefill_legacy.md",
    "source": "agi/llm/kernels/shaders/attention_prefill_legacy.js",
    "import": "/agi/llm/kernels/shaders/attention_prefill_legacy.js",
    "sourceHash": "84b9cc7369771e1ab0840029198d6b86169ad43cac083d2e188e8ab60d5fa88f",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/attention_prefill_tiled",
    "path": "agi/reference/llm/kernels/shaders/attention_prefill_tiled.md",
    "source": "agi/llm/kernels/shaders/attention_prefill_tiled.js",
    "import": "/agi/llm/kernels/shaders/attention_prefill_tiled.js",
    "sourceHash": "2bd1338a663e90012322d133cca6b4232f4511cda58df2d065a3c9998fe5de9f",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/dequant_row_dense",
    "path": "agi/reference/llm/kernels/shaders/dequant_row_dense.md",
    "source": "agi/llm/kernels/shaders/dequant_row_dense.js",
    "import": "/agi/llm/kernels/shaders/dequant_row_dense.js",
    "sourceHash": "7432a0f5e4b72d8d333dd13088662ffa2d660ffd1abaa6291d6cafbcda77c991",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ format = 'F16' } = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantF32",
        "kind": "function",
        "signature": "shaderDequantF32(options = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantF16",
        "kind": "function",
        "signature": "shaderDequantF16(options = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantBF16",
        "kind": "function",
        "signature": "shaderDequantBF16(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/dequant_row_k_quant",
    "path": "agi/reference/llm/kernels/shaders/dequant_row_k_quant.md",
    "source": "agi/llm/kernels/shaders/dequant_row_k_quant.js",
    "import": "/agi/llm/kernels/shaders/dequant_row_k_quant.js",
    "sourceHash": "f365dd3956e7b7dd8551e01dcff0fe1ed0cbecbd25aa07e1567f9f5b0bcdc310",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ quant = 'Q4_K' } = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantQ4K",
        "kind": "function",
        "signature": "shaderDequantQ4K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantQ5K",
        "kind": "function",
        "signature": "shaderDequantQ5K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderDequantQ6K",
        "kind": "function",
        "signature": "shaderDequantQ6K(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/dequant_row_q4_0",
    "path": "agi/reference/llm/kernels/shaders/dequant_row_q4_0.md",
    "source": "agi/llm/kernels/shaders/dequant_row_q4_0.js",
    "import": "/agi/llm/kernels/shaders/dequant_row_q4_0.js",
    "sourceHash": "bf440db4c3f2eabe87c23e376cda758d114115c9cb0512d0ae27e6d35d3d9546",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      },
      {
        "name": "shaderDequantQ40",
        "kind": "function",
        "signature": "shaderDequantQ40(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/dequant_row_q8_0",
    "path": "agi/reference/llm/kernels/shaders/dequant_row_q8_0.md",
    "source": "agi/llm/kernels/shaders/dequant_row_q8_0.js",
    "import": "/agi/llm/kernels/shaders/dequant_row_q8_0.js",
    "sourceHash": "c7a53b6f38c2ce8569d5aed7190abb3d3d0a7efe73153fa3fcb253ec9247236a",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      },
      {
        "name": "shaderDequantQ80",
        "kind": "function",
        "signature": "shaderDequantQ80(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/embedding_row_gather",
    "path": "agi/reference/llm/kernels/shaders/embedding_row_gather.md",
    "source": "agi/llm/kernels/shaders/embedding_row_gather.js",
    "import": "/agi/llm/kernels/shaders/embedding_row_gather.js",
    "sourceHash": "57a19c1055f6c8ef7d5dc919dc2b3fb1e0e754da8c2d54dd248c46a693f9005b",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matmul_k_quant",
    "path": "agi/reference/llm/kernels/shaders/matmul_k_quant.md",
    "source": "agi/llm/kernels/shaders/matmul_k_quant.js",
    "import": "/agi/llm/kernels/shaders/matmul_k_quant.js",
    "sourceHash": "6f5cb344061d162f343a57b251fe92f0addfe5f23362e42b31a92d79feb2f643",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ quant = 'Q4_K', workgroupSize = WORKGROUP_SIZE } = {})",
        "summary": ""
      },
      {
        "name": "shaderMatMulQ4K",
        "kind": "function",
        "signature": "shaderMatMulQ4K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderMatMulQ5K",
        "kind": "function",
        "signature": "shaderMatMulQ5K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderMatMulQ6K",
        "kind": "function",
        "signature": "shaderMatMulQ6K(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matmul_q4",
    "path": "agi/reference/llm/kernels/shaders/matmul_q4.md",
    "source": "agi/llm/kernels/shaders/matmul_q4.js",
    "import": "/agi/llm/kernels/shaders/matmul_q4.js",
    "sourceHash": "bf5931cdb4e80ca432641b4d1b61bd89cb410e6a4f2b98fdc0df8c79c0f48908",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matmul_q4_0",
    "path": "agi/reference/llm/kernels/shaders/matmul_q4_0.md",
    "source": "agi/llm/kernels/shaders/matmul_q4_0.js",
    "import": "/agi/llm/kernels/shaders/matmul_q4_0.js",
    "sourceHash": "547c12a0394481647058fd962907e7255df191670c3d36bb2dabaa360d77c927",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ workgroupSize = WORKGROUP_SIZE } = {})",
        "summary": ""
      },
      {
        "name": "shaderMatMulQ40",
        "kind": "function",
        "signature": "shaderMatMulQ40(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matvec_dense",
    "path": "agi/reference/llm/kernels/shaders/matvec_dense.md",
    "source": "agi/llm/kernels/shaders/matvec_dense.js",
    "import": "/agi/llm/kernels/shaders/matvec_dense.js",
    "sourceHash": "6a4151e047b9ad87008971b3e529a76da594e2ddecd27eda26969f798370100f",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ format = 'F32', workgroupSize = WORKGROUP_SIZE } = {})",
        "summary": ""
      },
      {
        "name": "shaderF32",
        "kind": "function",
        "signature": "shaderF32(options = {})",
        "summary": ""
      },
      {
        "name": "shaderF16",
        "kind": "function",
        "signature": "shaderF16(options = {})",
        "summary": ""
      },
      {
        "name": "shaderBF16",
        "kind": "function",
        "signature": "shaderBF16(options = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matvec_k_quant",
    "path": "agi/reference/llm/kernels/shaders/matvec_k_quant.md",
    "source": "agi/llm/kernels/shaders/matvec_k_quant.js",
    "import": "/agi/llm/kernels/shaders/matvec_k_quant.js",
    "sourceHash": "e69ee211a39f66e61461fc9d4638427b2d0425e9208c50256ede0bc22edd36dd",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ quant = 'Q4_K', workgroupSize = WORKGROUP_SIZE, useSubgroups = false } = {})",
        "summary": ""
      },
      {
        "name": "shaderQ4K",
        "kind": "function",
        "signature": "shaderQ4K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderQ5K",
        "kind": "function",
        "signature": "shaderQ5K(options = {})",
        "summary": ""
      },
      {
        "name": "shaderQ6K",
        "kind": "function",
        "signature": "shaderQ6K(options = {})",
        "summary": ""
      },
      {
        "name": "K_QUANT_LAYOUTS",
        "kind": "constant",
        "signature": "K_QUANT_LAYOUTS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matvec_q4_0",
    "path": "agi/reference/llm/kernels/shaders/matvec_q4_0.md",
    "source": "agi/llm/kernels/shaders/matvec_q4_0.js",
    "import": "/agi/llm/kernels/shaders/matvec_q4_0.js",
    "sourceHash": "e97dd089f8c186dd3ae4361c757a43e72d31b1716b5257eede66d3347fa660bd",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ workgroupSize = WORKGROUP_SIZE } = {})",
        "summary": ""
      },
      {
        "name": "shaderQ40",
        "kind": "function",
        "signature": "shaderQ40(options = {})",
        "summary": ""
      },
      {
        "name": "Q4_0_LAYOUT",
        "kind": "constant",
        "signature": "Q4_0_LAYOUT",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/matvec_q8_0",
    "path": "agi/reference/llm/kernels/shaders/matvec_q8_0.md",
    "source": "agi/llm/kernels/shaders/matvec_q8_0.js",
    "import": "/agi/llm/kernels/shaders/matvec_q8_0.js",
    "sourceHash": "eed60c3ea0ab2f3873ff1fee33847d5f06117df09ffa20fb6cc8374fc3dc350f",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ workgroupSize = WORKGROUP_SIZE } = {})",
        "summary": ""
      },
      {
        "name": "shaderQ80",
        "kind": "function",
        "signature": "shaderQ80(options = {})",
        "summary": ""
      },
      {
        "name": "Q8_0_LAYOUT",
        "kind": "constant",
        "signature": "Q8_0_LAYOUT",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rmsnorm_fused",
    "path": "agi/reference/llm/kernels/shaders/rmsnorm_fused.md",
    "source": "agi/llm/kernels/shaders/rmsnorm_fused.js",
    "import": "/agi/llm/kernels/shaders/rmsnorm_fused.js",
    "sourceHash": "74586ffb56cf3fa59378e75eb7281c427bb34fd60dced4298336ca65b6d241c4",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ hiddenSize = 1536, epsilon = '0.000001', workgroupSize = 256 } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rmsnorm_residual_fused",
    "path": "agi/reference/llm/kernels/shaders/rmsnorm_residual_fused.md",
    "source": "agi/llm/kernels/shaders/rmsnorm_residual_fused.js",
    "import": "/agi/llm/kernels/shaders/rmsnorm_residual_fused.js",
    "sourceHash": "1f5766acb0c1e2059787778865605e88764f3fe2d927c196194bec180ed1c997",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ hiddenSize = 1536, epsilon = '0.000001', workgroupSize = 256 } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rope_batch",
    "path": "agi/reference/llm/kernels/shaders/rope_batch.md",
    "source": "agi/llm/kernels/shaders/rope_batch.js",
    "import": "/agi/llm/kernels/shaders/rope_batch.js",
    "sourceHash": "20eea19d8f78862bb94d7fe482a80f789b660eafbddbb410a76a51a942ba17c8",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ theta = '10000.0' } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rope_fused",
    "path": "agi/reference/llm/kernels/shaders/rope_fused.md",
    "source": "agi/llm/kernels/shaders/rope_fused.js",
    "import": "/agi/llm/kernels/shaders/rope_fused.js",
    "sourceHash": "17235f8e330980030c2a833e5b794716589e9c5e3685a5a0036a6de59b9bc1c9",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ theta = '10000.0' } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rope_kv_append",
    "path": "agi/reference/llm/kernels/shaders/rope_kv_append.md",
    "source": "agi/llm/kernels/shaders/rope_kv_append.js",
    "import": "/agi/llm/kernels/shaders/rope_kv_append.js",
    "sourceHash": "933d3683015dab6cbd815f769f0af56c038d4ddaf5a1ff140eb77a4dc2a76f4a",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ theta = '10000.0' } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/rope_table",
    "path": "agi/reference/llm/kernels/shaders/rope_table.md",
    "source": "agi/llm/kernels/shaders/rope_table.js",
    "import": "/agi/llm/kernels/shaders/rope_table.js",
    "sourceHash": "fbba2d1a3dc39889670e58f0d3a59112f3c1cd06749f3c4ed1ba1c22efd023f1",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/sampling_topk_candidates",
    "path": "agi/reference/llm/kernels/shaders/sampling_topk_candidates.md",
    "source": "agi/llm/kernels/shaders/sampling_topk_candidates.js",
    "import": "/agi/llm/kernels/shaders/sampling_topk_candidates.js",
    "sourceHash": "da73f52b37c5cb358503cc4a1113fae24497da73fa41fe6f1eda2f47dd5343e1",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/sampling_topk_reduce",
    "path": "agi/reference/llm/kernels/shaders/sampling_topk_reduce.md",
    "source": "agi/llm/kernels/shaders/sampling_topk_reduce.js",
    "import": "/agi/llm/kernels/shaders/sampling_topk_reduce.js",
    "sourceHash": "7fc54814db3fb73dfeaae4f84d31d0ec89cedd658d28012073707551b7bac905",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/sampling_topk_topp",
    "path": "agi/reference/llm/kernels/shaders/sampling_topk_topp.md",
    "source": "agi/llm/kernels/shaders/sampling_topk_topp.js",
    "import": "/agi/llm/kernels/shaders/sampling_topk_topp.js",
    "sourceHash": "78523733d5de1973008e6076f8030d5a3b6fec799ec66c63b9b574fe765b92e6",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/sampling_topk_topp_legacy",
    "path": "agi/reference/llm/kernels/shaders/sampling_topk_topp_legacy.md",
    "source": "agi/llm/kernels/shaders/sampling_topk_topp_legacy.js",
    "import": "/agi/llm/kernels/shaders/sampling_topk_topp_legacy.js",
    "sourceHash": "2928af2857e53545e737df56ff73dae832500fa94221d2bb041f083bf94a0dd8",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader()",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/swiglu_fused",
    "path": "agi/reference/llm/kernels/shaders/swiglu_fused.md",
    "source": "agi/llm/kernels/shaders/swiglu_fused.js",
    "import": "/agi/llm/kernels/shaders/swiglu_fused.js",
    "sourceHash": "4a73d9ed716df74caa90480a22d65a8e9f9eb3b75ac4d140e44c7331b993dc07",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ activation = 'gelu_pytorch_tanh' } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/tied_embedding_logits",
    "path": "agi/reference/llm/kernels/shaders/tied_embedding_logits.md",
    "source": "agi/llm/kernels/shaders/tied_embedding_logits.js",
    "import": "/agi/llm/kernels/shaders/tied_embedding_logits.js",
    "sourceHash": "dc498a6e969dbbc616cec0e077793dae57c25bdba66946bf3bc4d230ddd454c7",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ workgroupSize = 128 } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/kernels/shaders/vector_ops",
    "path": "agi/reference/llm/kernels/shaders/vector_ops.md",
    "source": "agi/llm/kernels/shaders/vector_ops.js",
    "import": "/agi/llm/kernels/shaders/vector_ops.js",
    "sourceHash": "5ba610453422e04d830ce916cd74d91b328c7539d3e55f406a5a68e547f2a376",
    "summary": "",
    "exports": [
      {
        "name": "shader",
        "kind": "function",
        "signature": "shader({ op = 'add' } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/LLMRuntime",
    "path": "agi/reference/llm/runtime/LLMRuntime.md",
    "source": "agi/llm/runtime/LLMRuntime.js",
    "import": "/agi/llm/runtime/LLMRuntime.js",
    "sourceHash": "3ac2e9e534240f3d7d807becc5b3704433d2c207259b45ce1297d62a6a9160a1",
    "summary": "",
    "exports": [
      {
        "name": "LLMRuntime",
        "kind": "class",
        "signature": "class LLMRuntime",
        "summary": ""
      },
      {
        "name": "normalizeLocalRuntimeResourceUsage",
        "kind": "function",
        "signature": "normalizeLocalRuntimeResourceUsage(before, after)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/LLMSession",
    "path": "agi/reference/llm/runtime/LLMSession.md",
    "source": "agi/llm/runtime/LLMSession.js",
    "import": "/agi/llm/runtime/LLMSession.js",
    "sourceHash": "a589715cef48e22304b5327132e2dbecf7ed9fef2eb3e0217b595fcfe1b51310",
    "summary": "",
    "exports": [
      {
        "name": "LLMSession",
        "kind": "class",
        "signature": "class LLMSession",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/LogFormat",
    "path": "agi/reference/llm/runtime/LogFormat.md",
    "source": "agi/llm/runtime/LogFormat.js",
    "import": "/agi/llm/runtime/LogFormat.js",
    "sourceHash": "820e6a6e3aa5a9535c690613776ddcb3fec7f20dae1115ebaf2fb13e88057f1a",
    "summary": "",
    "exports": [
      {
        "name": "logRuntimeEvent",
        "kind": "function",
        "signature": "logRuntimeEvent(event, data = {}, options = {})",
        "summary": ""
      },
      {
        "name": "compactLogLine",
        "kind": "function",
        "signature": "compactLogLine(data)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/ModelRegistry",
    "path": "agi/reference/llm/runtime/ModelRegistry.md",
    "source": "agi/llm/runtime/ModelRegistry.js",
    "import": "/agi/llm/runtime/ModelRegistry.js",
    "sourceHash": "9c248d6bd650fc067d502d4283e2abe039a4c8941e8ad28e064728ffc35167fa",
    "summary": "",
    "exports": [
      {
        "name": "ModelRegistry",
        "kind": "class",
        "signature": "class ModelRegistry",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/PromptTemplate",
    "path": "agi/reference/llm/runtime/PromptTemplate.md",
    "source": "agi/llm/runtime/PromptTemplate.js",
    "import": "/agi/llm/runtime/PromptTemplate.js",
    "sourceHash": "2f159e1f9e527baab2001e447b934eb619724f46e9500d7175e1b6cf4b214557",
    "summary": "",
    "exports": [
      {
        "name": "PromptTemplate",
        "kind": "class",
        "signature": "class PromptTemplate",
        "summary": ""
      },
      {
        "name": "GEMMA4_SPECIAL_TOKENS",
        "kind": "constant",
        "signature": "GEMMA4_SPECIAL_TOKENS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/RuntimePerformanceMonitor",
    "path": "agi/reference/llm/runtime/RuntimePerformanceMonitor.md",
    "source": "agi/llm/runtime/RuntimePerformanceMonitor.js",
    "import": "/agi/llm/runtime/RuntimePerformanceMonitor.js",
    "sourceHash": "e689a583853ad1636940b5e3387cc35ae022450d76cea1f9d590b175af7de5a9",
    "summary": "",
    "exports": [
      {
        "name": "RuntimePerformanceMonitor",
        "kind": "class",
        "signature": "class RuntimePerformanceMonitor",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/Sampler",
    "path": "agi/reference/llm/runtime/Sampler.md",
    "source": "agi/llm/runtime/Sampler.js",
    "import": "/agi/llm/runtime/Sampler.js",
    "sourceHash": "8e51d5faea4a77bd54d89efcbb191ef8d0f848def16e5a06986a79d2f3f2cfce",
    "summary": "",
    "exports": [
      {
        "name": "Sampler",
        "kind": "class",
        "signature": "class Sampler",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/Tokenizer",
    "path": "agi/reference/llm/runtime/Tokenizer.md",
    "source": "agi/llm/runtime/Tokenizer.js",
    "import": "/agi/llm/runtime/Tokenizer.js",
    "sourceHash": "609fa36ca7371a78ab083580a8f7abbb72e3760cbffd6c269983fe6df7bca22c",
    "summary": "",
    "exports": [
      {
        "name": "Tokenizer",
        "kind": "class",
        "signature": "class Tokenizer",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/TokenizerWorkerClient",
    "path": "agi/reference/llm/runtime/TokenizerWorkerClient.md",
    "source": "agi/llm/runtime/TokenizerWorkerClient.js",
    "import": "/agi/llm/runtime/TokenizerWorkerClient.js",
    "sourceHash": "c39ec40134c451df5a47c9cd7d268ba56a0ed225c5e8fe09c39082644ee2d930",
    "summary": "",
    "exports": [
      {
        "name": "TokenizerWorkerClient",
        "kind": "class",
        "signature": "class TokenizerWorkerClient",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/runtime/ToolBridge",
    "path": "agi/reference/llm/runtime/ToolBridge.md",
    "source": "agi/llm/runtime/ToolBridge.js",
    "import": "/agi/llm/runtime/ToolBridge.js",
    "sourceHash": "8fc3a009d82b829a6e0cb140402c0dbf639db62c05914d68f28971618fa29105",
    "summary": "",
    "exports": [
      {
        "name": "ToolBridge",
        "kind": "class",
        "signature": "class ToolBridge",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/AtomicJSON",
    "path": "agi/reference/llm/storage/AtomicJSON.md",
    "source": "agi/llm/storage/AtomicJSON.js",
    "import": "/agi/llm/storage/AtomicJSON.js",
    "sourceHash": "29990a9482059b74d5fa6518e9f656deb9453857d478e78f4a71f8ccd07ba685",
    "summary": "Publish JSON through a verified staging file while retaining the previous revision until the replacement move succeeds.",
    "exports": [
      {
        "name": "replaceJSONCopyOnWrite",
        "kind": "function",
        "signature": "async replaceJSONCopyOnWrite(store, path, value, { validate = null, label = 'JSON record' } = {})",
        "summary": "Publish JSON through a verified staging file while retaining the previous revision until the replacement move succeeds."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/Checksum",
    "path": "agi/reference/llm/storage/Checksum.md",
    "source": "agi/llm/storage/Checksum.js",
    "import": "/agi/llm/storage/Checksum.js",
    "sourceHash": "06867b19c75e6c53f4c04fce2a8d2722ee576925a6a19000d9510d6ad6be3f59",
    "summary": "",
    "exports": [
      {
        "name": "checksumBytes",
        "kind": "function",
        "signature": "async checksumBytes(bytes, algorithm = 'SHA-256')",
        "summary": ""
      },
      {
        "name": "checksumString",
        "kind": "function",
        "signature": "async checksumString(text, algorithm = 'SHA-256')",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/ModelCache",
    "path": "agi/reference/llm/storage/ModelCache.md",
    "source": "agi/llm/storage/ModelCache.js",
    "import": "/agi/llm/storage/ModelCache.js",
    "sourceHash": "61ef650481657d194c49bd4d85b49717c67893238b22f837de8b311892c1d420",
    "summary": "",
    "exports": [
      {
        "name": "ModelCache",
        "kind": "class",
        "signature": "class ModelCache",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/ModelHandleStore",
    "path": "agi/reference/llm/storage/ModelHandleStore.md",
    "source": "agi/llm/storage/ModelHandleStore.js",
    "import": "/agi/llm/storage/ModelHandleStore.js",
    "sourceHash": "1ac899619e21fe46122a9f035b0e75e0416b01dacc5f4bdb23d0c8cf82bd26c0",
    "summary": "",
    "exports": [
      {
        "name": "ModelHandleStoreError",
        "kind": "class",
        "signature": "class ModelHandleStoreError extends Error",
        "summary": ""
      },
      {
        "name": "ModelHandleStore",
        "kind": "class",
        "signature": "class ModelHandleStore",
        "summary": ""
      },
      {
        "name": "prepareModelHandleRecord",
        "kind": "function",
        "signature": "prepareModelHandleRecord(value, expectedModelId = null)",
        "summary": ""
      },
      {
        "name": "prepareLastModelRecord",
        "kind": "function",
        "signature": "prepareLastModelRecord(value)",
        "summary": ""
      },
      {
        "name": "isFileSystemHandle",
        "kind": "function",
        "signature": "isFileSystemHandle(value)",
        "summary": ""
      },
      {
        "name": "queryHandlePermission",
        "kind": "function",
        "signature": "async queryHandlePermission(handle)",
        "summary": ""
      },
      {
        "name": "requestHandlePermission",
        "kind": "function",
        "signature": "async requestHandlePermission(handle)",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_RECORD_SCHEMA",
        "kind": "constant",
        "signature": "MODEL_HANDLE_RECORD_SCHEMA",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_RECORD_VERSION",
        "kind": "constant",
        "signature": "MODEL_HANDLE_RECORD_VERSION",
        "summary": ""
      },
      {
        "name": "MODEL_HANDLE_QUARANTINE_SCHEMA",
        "kind": "constant",
        "signature": "MODEL_HANDLE_QUARANTINE_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/OPFSStore",
    "path": "agi/reference/llm/storage/OPFSStore.md",
    "source": "agi/llm/storage/OPFSStore.js",
    "import": "/agi/llm/storage/OPFSStore.js",
    "sourceHash": "59bfc3241bd863dd7c1cb58aa9480cc22620a004d9526192f0bf9c249761403b",
    "summary": "",
    "exports": [
      {
        "name": "OPFSStore",
        "kind": "class",
        "signature": "class OPFSStore",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/storage/VFSConversationStore",
    "path": "agi/reference/llm/storage/VFSConversationStore.md",
    "source": "agi/llm/storage/VFSConversationStore.js",
    "import": "/agi/llm/storage/VFSConversationStore.js",
    "sourceHash": "db4e21ded540c16a1f9fca1007fb19c9a8ec726bafb03074d230f91ba64c8a47",
    "summary": "",
    "exports": [
      {
        "name": "VFSConversationStore",
        "kind": "class",
        "signature": "class VFSConversationStore",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "llm/workers/LLMLoadPlannerWorker",
    "path": "agi/reference/llm/workers/LLMLoadPlannerWorker.md",
    "source": "agi/llm/workers/LLMLoadPlannerWorker.js",
    "import": "/agi/llm/workers/LLMLoadPlannerWorker.js",
    "sourceHash": "b62dac9e7b377ca82e5c3176bde34aedf13eae2fa75c5d9b8b8e346dd17217b5",
    "summary": "",
    "exports": []
  },
  {
    "schemaVersion": 1,
    "title": "llm/workers/LLMTokenizerWorker",
    "path": "agi/reference/llm/workers/LLMTokenizerWorker.md",
    "source": "agi/llm/workers/LLMTokenizerWorker.js",
    "import": "/agi/llm/workers/LLMTokenizerWorker.js",
    "sourceHash": "769ae066cd2b0d1f52841f87b4094b164190165e88dba83d82d0546f8fed9d25",
    "summary": "",
    "exports": []
  },
  {
    "schemaVersion": 1,
    "title": "loader/ModelLoader",
    "path": "agi/reference/loader/ModelLoader.md",
    "source": "agi/loader/ModelLoader.js",
    "import": "/agi/loader/ModelLoader.js",
    "sourceHash": "6e3006b4ce7d497edf04a0fa47e99860b44bc97a2bd2b802477c1e06f521d40a",
    "summary": "",
    "exports": [
      {
        "name": "ModelLoader",
        "kind": "class",
        "signature": "class ModelLoader",
        "summary": ""
      },
      {
        "name": "modelLoaderVertexDedupHash32",
        "kind": "function",
        "signature": "modelLoaderVertexDedupHash32(posBits, pOff, uvBits = null, uOff = 0)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "network/TrainingStateChannel",
    "path": "agi/reference/network/TrainingStateChannel.md",
    "source": "agi/network/TrainingStateChannel.js",
    "import": "/agi/network/TrainingStateChannel.js",
    "sourceHash": "3bfd7433f6e110a712aec26fdaab4b2fa1bc52d841d7372ea1027c06054fcb9c",
    "summary": "Bind AGI Studio telemetry to the standard State Channel stack. The training runtime is authoritative; same-origin tabs need no setup, and callers hosted inside WebGPU OS may pass NetworkDriver to publish over mesh.",
    "exports": [
      {
        "name": "AgiTrainingStateChannel",
        "kind": "class",
        "signature": "class AgiTrainingStateChannel",
        "summary": "Bind AGI Studio telemetry to the standard State Channel stack. The training runtime is authoritative; same-origin tabs need no setup, and callers hosted inside WebGPU OS may pass NetworkDriver to publish over mesh."
      },
      {
        "name": "createAgiTrainingStateChannel",
        "kind": "function",
        "signature": "createAgiTrainingStateChannel(options = {})",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_CHANNEL_ID",
        "kind": "constant",
        "signature": "AGI_TRAINING_CHANNEL_ID",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_ROUTE_ID",
        "kind": "constant",
        "signature": "AGI_TRAINING_ROUTE_ID",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_CONTRACT",
        "kind": "constant",
        "signature": "AGI_TRAINING_CONTRACT",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/BreathReservoirState",
    "path": "agi/reference/particle_voice/articulatory/BreathReservoirState.md",
    "source": "agi/particle_voice/articulatory/BreathReservoirState.js",
    "import": "/agi/particle_voice/articulatory/BreathReservoirState.js",
    "sourceHash": "5d29378ba659dc894212c40bace8aca69114befc9e97127e251717dec025df5f",
    "summary": "BreathReservoirState.js — Phase 2 Voice Box state schema (`v0`). \"Lung pressure, compliance, target subglottal pressure\" per `ARCHITECTURE.md` §4 — the JS-side state container wrapping `nn/kernels/articulatory/breath_reservoir.js`'s per-instance recurrence (`pressure[n] = pressure[n-1] + (target[n] - pressure[n-1]) * lagRate`, proven against `risk/BreathReservoir.js` (R6A)). This module owns the `lagRate` derivation from `compliance`/`sampleRate` (the caller-computed value that kernel's docstring says it expects pre-computed, not recomputed in-shader) and the timestamped state container the kernel's `initial_pressure`/`pressure_out` buffers round-trip through across chunks — it does not itself dispatch the kernel (that is `ParticleTract.js`'s job once a real model exists to drive it).",
    "exports": [
      {
        "name": "lagRateFromCompliance",
        "kind": "function",
        "signature": "lagRateFromCompliance(compliance, sampleRate)",
        "summary": "`lagRate = 1 - exp(-1/(compliance*sampleRate))` — matches `risk/BreathReservoir.js` (R6A)'s proven formula exactly; `breath_reservoir.js`'s own docstring requires this be computed by the caller, not the kernel."
      },
      {
        "name": "createBreathReservoirState",
        "kind": "function",
        "signature": "createBreathReservoirState({ sampleIndex, pressure, compliance, targetSubglottalPressure })",
        "summary": ""
      },
      {
        "name": "stepBreathReservoirState",
        "kind": "function",
        "signature": "stepBreathReservoirState(state, sampleRate)",
        "summary": "Advance one `BreathReservoirState` by one sample using the exact same recurrence `breath_reservoir.js`/`risk/BreathReservoir.js` implement — the CPU-side single-sample equivalent, useful for tests and for any non-GPU fallback path, not a replacement for the GPU kernel's chunked dispatch."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/FormantSolver",
    "path": "agi/reference/particle_voice/articulatory/FormantSolver.md",
    "source": "agi/particle_voice/articulatory/FormantSolver.js",
    "import": "/agi/particle_voice/articulatory/FormantSolver.js",
    "sourceHash": "b8dc73ed6ea31c7ecbca2bf1e03f0f79e8c2d19e55bbfc61e3190aac2f2651d9",
    "summary": "FormantSolver.js — Phase 3 ParticleVoice articulatory calibration. Measures the resonances a Kelly-Lochbaum area profile actually produces, and inverts that: given target formants, searches for an area profile that hits them. ## Why this exists Vowel identity in speech is carried almost entirely by **F1 and F2**. An area profile invented from articulatory adjectives (\"high\", \"back\", \"rounded\") produces *some* formants, but nothing forces them to be the RIGHT ones — and if they are wrong, every vowel collapses toward a neutral timbre and the speech is unintelligible no matter how clean the pipeline is. That was the state before this module: `ArticulationHead`'s height/backness → area mapping was plausible but uncalibrated. Defining vowels by their measured formant targets and SOLVING for the geometry inverts the dependency: the targets are citable data (Peterson & Barney 1952 / Hillenbrand et al. 1995 male averages, whose vowel set maps exactly onto `spec/PhonemeSet-v0.md`'s ten monophthongs), and the geometry becomes a derived quantity that stays correct if the section count or sample rate changes. ## The response is measured with the SAME recurrence the GPU runs `impulseResponse()` reimplements `tract_waveguide.js`'s scattering recurrence on the CPU rather than using an analytic transfer function. That is deliberate: an analytic formula would be a second model that could disagree with the kernel, and then a \"calibrated\" vowel would not actually be calibrated for the thing doing the synthesis. In particular this omits the lip first-difference, matching `ParticleTract`'s `radiation_difference = 0` (its source is already a flow derivative).",
    "exports": [
      {
        "name": "impulseResponse",
        "kind": "function",
        "signature": "impulseResponse(areas, { glottalReflection, lipReflection, responseLength, wallLoss = 1 })",
        "summary": "Impulse response of the lossless KL chain, mirroring `tract_waveguide.js`'s recurrence (and, like `ParticleTract`, WITHOUT the lip first difference)."
      },
      {
        "name": "findFormants",
        "kind": "function",
        "signature": "findFormants(areas, sampleRate, options = {})",
        "summary": "Resonance frequencies of an area profile, lowest first."
      },
      {
        "name": "profileFromParams",
        "kind": "function",
        "signature": "profileFromParams(params, numSections)",
        "summary": "Build the profile this solver searches over. `constrictionWidth` exists because a single control point is not enough for high front vowels. `shapedAreaProfile` interpolates LINEARLY between control points, so one narrow point produces a smooth V — a tract that tapers and re-widens with no distinct cavities. /i/ needs the opposite: a genuinely SHORT front cavity (F2 = 2290 Hz implies c/(4*F2) ~ 3.7 cm) sharply separated from a long back cavity. Giving the constriction a WIDTH creates that two-cavity structure, and with it F2 values a single point cannot reach (an earlier 3-point version topped out at 1889 Hz against IY's 2290 Hz target, 333 cents low, which made IY and IH nearly identical in F2). A width of 0 degenerates to the original 3-point shape, so the parametrisation strictly extends the old one rather than replacing it."
      },
      {
        "name": "fitVowelAreas",
        "kind": "function",
        "signature": "fitVowelAreas(target, numSections, sampleRate, options = {})",
        "summary": "Search for an area profile whose first two resonances match `target`. Error is measured in the LOG-frequency domain, because formant perception is roughly logarithmic: being 100 Hz off matters far more at F1 = 270 than at F2 = 2290, and a linear error would happily sacrifice F1 accuracy to shave a little off F2. Exhaustive over a coarse grid, then a local refinement pass. Exhaustive rather than gradient-based on purpose: the map from geometry to formants is non-monotonic (moving a constriction can swap which cavity dominates F2), so a descent method lands in whichever basin it started in."
      },
      {
        "name": "fitTargets",
        "kind": "function",
        "signature": "fitTargets(targets, numSections, sampleRate, options = {})",
        "summary": "Fit every entry in a formant-target table. Slow (a full grid per entry) — intended for a calibration tool/test, not a hot path."
      },
      {
        "name": "fitAllVowels",
        "kind": "function",
        "signature": "fitAllVowels(numSections, sampleRate, options = {})",
        "summary": ""
      },
      {
        "name": "fitAllApproximants",
        "kind": "function",
        "signature": "fitAllApproximants(numSections, sampleRate, options = {})",
        "summary": ""
      },
      {
        "name": "tractLengthFor",
        "kind": "function",
        "signature": "tractLengthFor(numSections, sampleRate)",
        "summary": "The tract length a section count represents, for reporting alongside a fit."
      },
      {
        "name": "VOWEL_FORMANTS",
        "kind": "constant",
        "signature": "VOWEL_FORMANTS",
        "summary": "Peterson & Barney (1952) male averages, Hz. Their vowel set — heed, hid, head, had, hod, hawed, hood, who'd, hud, heard — is exactly this project's ten monophthongs, so no mapping or interpolation is needed. F3 is carried for `ER` only, where the unusually LOW F3 is the defining rhotic cue; for the others F1/F2 determine identity."
      },
      {
        "name": "VOWEL_AREA_PARAMS",
        "kind": "constant",
        "signature": "VOWEL_AREA_PARAMS",
        "summary": "The solved geometry for each vowel, produced by `fitAllVowels()` and baked here so the runtime does not pay for the search (a full grid per vowel takes tens of seconds). `tests/particle-voice/vowel-formants.html` re-verifies this table against the targets cheaply on every run, so it cannot silently go stale if the section count or sample rate changes — the check fails and the table is re-fitted. Fitted at **32 sections / 64 kHz, two constrictions, wall loss included**. Measured F1/F2 vs target, worst case **21 cents** across all ten: IY 270/2291   IH 390/1989   EH 530/1844   AE 661/1724   AH 640/1190 ER 490/1350   AA 730/1090   AO 569/850    UH 438/1019   UW 300/867 Three structural changes got here, none of them tuning: 1. **Doubling the resolution** (16 -> 32 sections, via 32 -> 64 kHz) made a short front cavity representable, which is what front vowels need. IY's F2 went 1926 -> 2211 Hz on its own. 2. **A second constriction** fixed the back rounded vowels, which a single narrowing simply could not reach: AO's F2 was 1130 Hz against an 840 Hz target (513 cents) and no better solution existed anywhere in the search space, because a low F2 requires a long back cavity AND lip rounding acting together. Worst error 513 -> 60 cents. 3. **Including WALL LOSS in the calibration**, which improved it again (60 -> 21 cents). That is not a coincidence: loss gives the formants realistic bandwidth, and damped peaks are better separated and more reliably located than the near-singular peaks of a lossless tube. It also means the fit is now against the same damped tube that renders. Every area respects `MIN_VOWEL_AREA_CM2`, so no vowel is realised with a constriction tight enough to whistle or hiss."
      },
      {
        "name": "APPROXIMANT_FORMANTS",
        "kind": "constant",
        "signature": "APPROXIMANT_FORMANTS",
        "summary": "Formant targets for the four approximants, Hz. These are consonants only in their distribution — acoustically /w j r l/ are \"vowel-like consonants\" (semivowels), and a generic `MANNER_AREA` constriction cannot produce any of them. That is why they were inaudible as themselves: a single 0.85 cm^2 narrowing at a place position yields something vowel-shaped and roughly neutral, so /r/, /l/, /w/ and /y/ all collapsed into the same glide. Treating them like vowels — targets first, geometry solved — is the only way they become distinct. Values from the acoustic-phonetics literature (Espy-Wilson's prevocalic measurements; the Delaware ASEL synthesis tables): /w/ 250/750   — an \"extreme /u/\": lower F1 and F2 than the vowel /r/ 350/1000  — F1/F2 like a central rounded vowel... /l/ 400/1075  — ...nearly the same F1/F2 as /r/ /y/ 300/2450  — an \"extreme /i/\" Note the F1/F2 values for /r/ and /l/ are nearly identical. **F3 is what separates them**, almost entirely: /r/'s F3 falls below 2000 Hz (often close to F2, which is its single most salient property), while /l/'s sits at or above 2500 Hz. `f3` is carried here for that reason, and `f3Weight` marks where matching it actually matters — for /r/ and /l/ an F3 error is not a refinement, it is the difference between the two phonemes."
      },
      {
        "name": "APPROXIMANT_AREA_PARAMS",
        "kind": "constant",
        "signature": "APPROXIMANT_AREA_PARAMS",
        "summary": "Solved approximant geometry, same provenance and staleness guard as `VOWEL_AREA_PARAMS`. Fitted at 32 sections / 64 kHz, with the second constriction AND wall loss (both must match `DEFAULT_TRACT_CONFIG`, or this calibrates a different tube than the one that renders). Measured F1/F2/F3 against target: W 249/852/2113  (250/750/2500) R 337/1137/1845 (350/1000/1500) L 398/1075/2556 (400/1075/2550) Y 299/2402/2688 (300/2450/2600) L is near-exact on all three and Y is within 50 Hz on F1/F2. The decisive property holds: **R's F3 sits at 1845 Hz and L's at 2556 Hz**, a 711 Hz separation, so the one cue that distinguishes /r/ from /l/ genuinely exists. Before calibration both were a generic manner constriction with NO F3 contrast at all — literally the same sound, which is why the user could hear neither. The second constriction is what made this reachable. With a single one, R's F3 was 2166 Hz (not rhotic at all), W's F2 was 1091 against 750, and Y's F2 was 1541 against 2450. R's F3 is still ~345 Hz above target, the remaining gap being the sublingual cavity that a 1-D area function cannot represent at all."
      },
      {
        "name": "MIN_VOWEL_AREA_CM2",
        "kind": "constant",
        "signature": "MIN_VOWEL_AREA_CM2",
        "summary": "Smallest cross-section, in cm^2, a VOWEL is allowed anywhere in the tract. Without this the search is free to buy formant accuracy with impossible geometry: an unconstrained fit put AA's constriction at 0.15 cm^2, which is TIGHTER than `ArticulationHead`'s fricative constant (0.14). Two things are wrong with that. Physically, a channel that narrow generates turbulence at speech airflow rates — it would be a fricative, not a vowel, and a vowel that hisses is not the vowel you asked for. Numerically, the extreme area ratio drives junction reflection coefficients toward +-1, giving absurdly high-Q resonances that ring. 0.35 cm^2 sits just above the turbulence onset region while still allowing the tight constrictions high vowels genuinely need (measured vowel minima run from ~0.3 cm^2 for /i/ upward)."
      },
      {
        "name": "DEFAULT_SOLVER_OPTIONS",
        "kind": "constant",
        "signature": "DEFAULT_SOLVER_OPTIONS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/NeuralPhysiologyState",
    "path": "agi/reference/particle_voice/articulatory/NeuralPhysiologyState.md",
    "source": "agi/particle_voice/articulatory/NeuralPhysiologyState.js",
    "import": "/agi/particle_voice/articulatory/NeuralPhysiologyState.js",
    "sourceHash": "e759a67f60b7e17ad72176a031511d060010bc247e22176a052bfdcf2f393c76",
    "summary": "NeuralPhysiologyState.js — Phase 2 Voice Box state schema (`v0`). The model's predicted per-frame physiology state, per `ARCHITECTURE.md` §4: four control heads plus an uncertainty head, version `0x00010000`. This is a `v0` working definition of each head's field set — the heads themselves are named and required by the plan, but their exact field composition was not otherwise specified anywhere in the frozen `spec/` documents, so the fields chosen here are deliberately the smallest set that maps DIRECTLY onto an already-built, already-tested GPU kernel's input parameters (not invented ahead of a consumer): - `RespiratoryDriveHead.targetSubglottalPressure` → drives `BreathReservoirState`/`nn/kernels/articulatory/breath_reservoir.js`. - `GlottalStateHead` (`t0Samples`/`teSamples`/`tpSamples`/`taSamples`/ `ee`) → the exact parameter names `nn/kernels/articulatory/glottal_lf.js` takes per instance. - `TractGestureHead.areas` → the area profile `nn/kernels/articulatory/tract_transfer.js`/`tract_waveguide.js` consume. - `ArticulationEventHead` (`constrictionIndex`/`constrictionAmplitude`/ `nasalCoupling`) → drives `constriction_noise.js`/`nasal_junction.js` injection points (the actual multi-port coupling math those two kernels' docstrings still flag as deferred — this head only carries the control values, not a coupling implementation). - `UncertaintyHead.confidence` → a single scalar per-frame confidence, the minimal representation until a real model exists to justify a richer (e.g. per-head covariance) shape. Like every other `v0` document in this project, this field set may still change before a `v1` freeze — it is not a claim that these are the final head shapes, only that they are usable today and traceable to real kernel inputs rather than speculative. Per `ARCHITECTURE.md` §4's timestamping rule, every state object here carries the absolute sample counter it was produced at (`sampleIndex`), never a chunk-relative index.",
    "exports": [
      {
        "name": "createNeuralPhysiologyState",
        "kind": "function",
        "signature": "createNeuralPhysiologyState({ sampleIndex, respiratoryDrive, glottalState, tractGesture, articulationEvent = null, uncertainty = { confidence: 1 }, })",
        "summary": ""
      },
      {
        "name": "validateRespiratoryDriveHead",
        "kind": "function",
        "signature": "validateRespiratoryDriveHead(head)",
        "summary": ""
      },
      {
        "name": "validateGlottalStateHead",
        "kind": "function",
        "signature": "validateGlottalStateHead(head)",
        "summary": ""
      },
      {
        "name": "validateTractGestureHead",
        "kind": "function",
        "signature": "validateTractGestureHead(head)",
        "summary": ""
      },
      {
        "name": "validateArticulationEventHead",
        "kind": "function",
        "signature": "validateArticulationEventHead(head)",
        "summary": ""
      },
      {
        "name": "validateUncertaintyHead",
        "kind": "function",
        "signature": "validateUncertaintyHead(head)",
        "summary": ""
      },
      {
        "name": "NEURAL_PHYSIOLOGY_STATE_VERSION",
        "kind": "constant",
        "signature": "NEURAL_PHYSIOLOGY_STATE_VERSION",
        "summary": "NeuralPhysiologyState.js — Phase 2 Voice Box state schema (`v0`). The model's predicted per-frame physiology state, per `ARCHITECTURE.md` §4: four control heads plus an uncertainty head, version `0x00010000`. This is a `v0` working definition of each head's field set — the heads themselves are named and required by the plan, but their exact field composition was not otherwise specified anywhere in the frozen `spec/` documents, so the fields chosen here are deliberately the smallest set that maps DIRECTLY onto an already-built, already-tested GPU kernel's input parameters (not invented ahead of a consumer): - `RespiratoryDriveHead.targetSubglottalPressure` → drives `BreathReservoirState`/`nn/kernels/articulatory/breath_reservoir.js`. - `GlottalStateHead` (`t0Samples`/`teSamples`/`tpSamples`/`taSamples`/ `ee`) → the exact parameter names `nn/kernels/articulatory/glottal_lf.js` takes per instance. - `TractGestureHead.areas` → the area profile `nn/kernels/articulatory/tract_transfer.js`/`tract_waveguide.js` consume. - `ArticulationEventHead` (`constrictionIndex`/`constrictionAmplitude`/ `nasalCoupling`) → drives `constriction_noise.js`/`nasal_junction.js` injection points (the actual multi-port coupling math those two kernels' docstrings still flag as deferred — this head only carries the control values, not a coupling implementation). - `UncertaintyHead.confidence` → a single scalar per-frame confidence, the minimal representation until a real model exists to justify a richer (e.g. per-head covariance) shape. Like every other `v0` document in this project, this field set may still change before a `v1` freeze — it is not a claim that these are the final head shapes, only that they are usable today and traceable to real kernel inputs rather than speculative. Per `ARCHITECTURE.md` §4's timestamping rule, every state object here carries the absolute sample counter it was produced at (`sampleIndex`), never a chunk-relative index."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/ParticleTract",
    "path": "agi/reference/particle_voice/articulatory/ParticleTract.md",
    "source": "agi/particle_voice/articulatory/ParticleTract.js",
    "import": "/agi/particle_voice/articulatory/ParticleTract.js",
    "sourceHash": "a294f6f6db8a6fbe43ad637c6ee1e5575dd74b882281b1c96b833704f6c93497",
    "summary": "ParticleTract.js — Phase 2 ParticleVoice Voice Box orchestrator. The GPU-resident articulatory synthesizer: composes the already-verified Phase 2 articulatory kernels into audible speech, one fixed-size chunk at a time, with all cross-chunk state persisted ON THE GPU (no CPU round trip mid-pipeline). This is the production counterpart of R6's pure-CPU `risk/VoiceBoxWaveguideProbe.js`, and deliberately reproduces the same four controllable behaviors that probe proved: stable vowels, nasal coupling, closure/release/frication, and pitch sweeps. Signal graph per chunk (7 GPU passes + 4 state copies, ONE submit): glottal_lf ────────────────► voicing ──┐ ├─ signal_add ─► source_flow ─┐ constriction_noise(asp seed) ─► asp ────┘                            │ ▼ constriction_noise(fric seed) ─► frication ──────► tract_waveguide (oral) │         │ radiated ◄─────────┘         └──► nasal_tap │                                │ │            nasal_junction ◄────┘ │                   │ └── signal_add ◄── radiated │ ▼  final PCM Design decisions worth knowing: - **Frication is injected at the constriction, not the glottis.** The frication noise buffer goes to `tract_waveguide.js`'s `constriction_noise` input and is added to the right-going wave at `constrictionIndex`, so only the cavity in FRONT of the constriction filters it. Mixing it into `source_flow` instead would make every fricative sound like a whisper. Aspiration, which really is glottal, IS mixed into `source_flow`. - **No `lip_radiation` pass.** `tract_waveguide.js` already applies the `outputRaw - prevOutputRaw` radiation first-difference internally (exactly as R6's `step()` does). Running `lip_radiation.js` on its output would differentiate twice and produce a thin, bass-less buzz. That kernel exists for callers holding an undifferentiated signal. - **Fixed `chunkSamples`.** `tract_waveguide.js`/`nasal_junction.js` pack their radiated signal and persisted state into one buffer whose internal offsets depend on `num_samples`, so a varying chunk size would move every offset and invalidate the pre-built bind groups. A fixed chunk size is also what a real-time audio bridge wants anyway (`streaming/VoiceChunker.js` schedules in fixed frame buckets), so it is required here rather than worked around. - **Reflection coefficients are computed CPU-side** each chunk via `risk/KellyLochbaumWaveguide.js`'s proven `reflectionCoefficients`, matching `tract_waveguide.js`'s stated contract. Changing `areas` between chunks is how closure/release is articulated. **Visualization is optional and structurally incapable of changing the PCM.** `ARCHITECTURE.md` §4 requires visualization to be \"bit-identically disable-able without changing rendered PCM\". Here that is not a runtime convention but a structural property: when `visualFramesPerChunk` is 0 (the default) the visualization buffers, pipeline and dispatches are never created at all, and when it is enabled every visualization buffer is WRITE-ONLY from the acoustic path's perspective — the field is copied OUT of `tract_waveguide`/`nasal_junction`'s already-final packed outputs, and nothing in the visualization path is ever read back into the acoustic path. `visual_state_decimate.js` makes the same guarantee at kernel level (it has no read_write access to anything the acoustic path reads). Known limitations, stated honestly rather than papered over: - `constrictionIndex` and `nasalCoupling` are DISPATCH-WIDE (they live in the kernels' uniform structs, not per-instance storage arrays), so all instances of one `ParticleTract` share them. Per-instance variation would require promoting both to storage buffers. They are therefore top-level controls here, not per-instance ones, so the API does not imply a capability that does not exist. - The nasal tap is a read-only observation of the oral tract, not a rigorous 3-port scattering junction — the oral tract is not loaded by the nasal branch. This matches R6 exactly and is noted in `tract_waveguide.js`'s docstring too. - The visualization field carries the THREE per-sample solver signals the kernels actually expose (oral radiated, nasal tap, nasal radiated), not one channel per tube section. `ARCHITECTURE.md` §4 describes the richer per-section field, but `tract_waveguide.js` does not emit a `[section, sample]` field — only its radiated signal, final state and the single tapped section. Adding one would mean a `numSections * chunkSamples` output block (~512 KB per chunk at 32 sections / 1024 samples) allocated whether or not visualization is on. That is a deliberate follow-up, not an oversight; the three channels here are enough to drive a ribbon/heatmap of what the solver is actually radiating.",
    "exports": [
      {
        "name": "ParticleTract",
        "kind": "class",
        "signature": "class ParticleTract",
        "summary": ""
      },
      {
        "name": "sectionsForLength",
        "kind": "function",
        "signature": "sectionsForLength(lengthM, sampleRate, speedOfSound = SPEED_OF_SOUND_M_PER_S)",
        "summary": ""
      },
      {
        "name": "lengthForSections",
        "kind": "function",
        "signature": "lengthForSections(numSections, sampleRate, speedOfSound = SPEED_OF_SOUND_M_PER_S)",
        "summary": ""
      },
      {
        "name": "createParticleTract",
        "kind": "function",
        "signature": "createParticleTract(device, options)",
        "summary": ""
      },
      {
        "name": "SPEED_OF_SOUND_M_PER_S",
        "kind": "constant",
        "signature": "SPEED_OF_SOUND_M_PER_S",
        "summary": "Boundary coefficients are R6's proven values. The SECTION COUNTS are not — they are derived from physics, and R6's were wrong. ## Section count is not a free parameter: it sets the tract LENGTH In this waveguide a wave advances one section per sample (`right[i] -> right[i+1]`), so each section is one sample of travel: sectionLength = c / sampleRate tractLength   = numSections * c / sampleRate The count is therefore *determined* by the sample rate and the length you want. At 32 kHz with c = 340 m/s a section is 1.06 cm, so a 17.5 cm adult vocal tract needs **16 sections**, not 32. R6 used 32 sections at 32 kHz, which models a **34 cm** tube — twice human length. Nothing errors: the model is a perfectly valid tube, just the wrong one. Since a quarter-wave resonator's formants are `(2k-1)*c/(4L)`, doubling L puts EVERY formant an octave too low — F1 at ~260 Hz instead of ~500 Hz. That is not a subtle detune; it moves the formants out of the range speech sounds occupy, which is audible as muffled, \"underwater\", vowel-less noise no matter how clean the rest of the pipeline is. It was measured directly (F1 261 Hz, implied length 32.6 cm) by the uniform-tube calibration check in `tests/particle-voice/voicebox-audio.html`, which now guards it. Note the literature's 2x trap: a HALF-sample-delay Kelly-Lochbaum variant has `sectionLength = c/(2*sampleRate)`, for which 32 sections at 32 kHz WOULD be right (that is why published models quote e.g. 44 segments at 44.1 kHz). This implementation is the full-sample form, so the full-sample relation applies. The calibration test asserts the resulting length empirically rather than trusting either convention."
      },
      {
        "name": "ADULT_TRACT_LENGTH_M",
        "kind": "constant",
        "signature": "ADULT_TRACT_LENGTH_M",
        "summary": ""
      },
      {
        "name": "ADULT_NASAL_LENGTH_M",
        "kind": "constant",
        "signature": "ADULT_NASAL_LENGTH_M",
        "summary": ""
      },
      {
        "name": "DEFAULT_TRACT_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_TRACT_CONFIG",
        "summary": ""
      },
      {
        "name": "VISUAL_CHANNELS",
        "kind": "constant",
        "signature": "VISUAL_CHANNELS",
        "summary": "The per-sample solver signals the visualization field carries, in channel order. Exported so a renderer indexes channels by name instead of hard-coding 0/1/2."
      },
      {
        "name": "DEFAULT_LF_SHAPE",
        "kind": "constant",
        "signature": "DEFAULT_LF_SHAPE",
        "summary": "LF shape defaults. `openQuotient` matches R6's `createGlottalSource({ openQuotient: 0.6 })`; the other two are LF-specific ratios with no R6 equivalent (R6 used the raised-cosine \"LF-lite\" shape that has no Tp/Ta at all)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/PhysiologyFeedbackState",
    "path": "agi/reference/particle_voice/articulatory/PhysiologyFeedbackState.md",
    "source": "agi/particle_voice/articulatory/PhysiologyFeedbackState.js",
    "import": "/agi/particle_voice/articulatory/PhysiologyFeedbackState.js",
    "sourceHash": "adbb8adf62de04bc7038811cd5b7a79a24d3aeef734d1dd099757c1e254922ff",
    "summary": "PhysiologyFeedbackState.js — Phase 2 Voice Box state schema (`v0`). `ARCHITECTURE.md` §4's `PhysiologyFeedbackState`: \"a reduced/decimated feedback signal from the acoustic solve back toward the model\". The JS-side timestamped container for one `nn/kernels/articulatory/physiology_feedback_reduce.js` dispatch's output — per-instance (mean, RMS) pairs, one pair per fixed-size frame — distinct from `TractStateSnapshot.js`'s multi-channel visualization field (`visual_state_decimate.js`'s output): this shape is what the model's control loop reads back, not what a rig/visual system reads. Every consumer of this state should treat it as internal-physiology feedback, not a real acoustic measurement — matching `spec/ActivityContract-v0.md` §8's `GeneratedPhysiologyEnergy` / `RenderedOutputEnergy` / `ObservedInputEnergy` three-domain separation (this is `GeneratedPhysiologyEnergy`'s reduced form, never to be confused with a microphone-observed or rendered-PCM-measured energy value).",
    "exports": [
      {
        "name": "createPhysiologyFeedbackState",
        "kind": "function",
        "signature": "createPhysiologyFeedbackState({ sampleIndex, numFrames, mean, rms })",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/TractStateSnapshot",
    "path": "agi/reference/particle_voice/articulatory/TractStateSnapshot.md",
    "source": "agi/particle_voice/articulatory/TractStateSnapshot.js",
    "import": "/agi/particle_voice/articulatory/TractStateSnapshot.js",
    "sourceHash": "10ed0ac0a50c92dc1214e3d28ce96a141d247a7ad534c1cf41922b5618cb408d",
    "summary": "TractStateSnapshot.js — Phase 2 Voice Box state schema (`v0`). `ARCHITECTURE.md` §4's `TractStateSnapshot`: \"low-rate timestamped state for rigs/visualization, decimated from the per-sample waveguide state\". The JS-side timestamped container for one `nn/kernels/articulatory/visual_state_decimate.js` dispatch's output — a `[channel, frame]` box-averaged field (one channel per tube section, per that kernel's docstring), NOT the model-feedback (mean, RMS) shape `PhysiologyFeedbackState.js` wraps (`physiology_feedback_reduce.js`'s output) — the two are read by different consumers (a rig/visual system here, the model's control loop there) and were kept as separate kernels for exactly this reason (see `visual_state_decimate.js`'s own docstring). Per `ARCHITECTURE.md` §4's requirement that visualization \"must be bit-identically disable-able without changing rendered PCM\", this container holds ONLY derived/decimated data for display — nothing here is ever read back into the acoustic path, matching `visual_state_decimate.js`'s structural read-only guarantee.",
    "exports": [
      {
        "name": "createTractStateSnapshot",
        "kind": "function",
        "signature": "createTractStateSnapshot({ sampleIndex, numChannels, numFrames, field })",
        "summary": ""
      },
      {
        "name": "channelSeries",
        "kind": "function",
        "signature": "channelSeries(snapshot, channel)",
        "summary": "Reads channel `channel`'s decimated time series out of a snapshot's flat `[channel, frame]` field — matches `visual_state_decimate.js`'s `(inst*num_channels+ch)*num_frames+frame` packing (single-instance view; the caller selects `channel`/`instance` slicing upstream of this helper)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/VoiceAnatomyProfile",
    "path": "agi/reference/particle_voice/articulatory/VoiceAnatomyProfile.md",
    "source": "agi/particle_voice/articulatory/VoiceAnatomyProfile.js",
    "import": "/agi/particle_voice/articulatory/VoiceAnatomyProfile.js",
    "sourceHash": "0bcf2ed0d4d2dbd00062ed90e14f1598429d046845c4ec3d16f83c27662cb386",
    "summary": "VoiceAnatomyProfile.js — Phase 2 Voice Box state schema (`v0`). `ARCHITECTURE.md` §4's `CharacterAnatomyState`: \"persistent per-character morphology (equivalent role to `PRVC-v0`'s `formantScale`/pitch-range calibration, but for non-cloned/authored characters rather than captured voices)\". This is the STATIC per-voice data that stays constant across frames — the rest-position oral tube area profile, optional nasal branch area profile, and the three boundary reflection coefficients `nn/kernels/articulatory/tract_waveguide.js`/`nasal_junction.js` require (`glottalReflection`/`lipReflection`/`nostrilReflection`, all magnitude `< 1` for BIBO stability per `risk/KellyLochbaumWaveguide.js` (R6)) — as distinct from `NeuralPhysiologyState`'s `TractGestureHead`, which is the per-FRAME predicted deviation/gesture applied on top of this rest profile, not a replacement for it. `risk/VoiceBoxWaveguideProbe.js` (R6/R6A) already proved out `NUM_SECTIONS = 32`/`NASAL_SECTIONS = 16`, `GLOTTAL_REFLECTION = 0.85`, `LIP_REFLECTION = -0.9`, `NOSTRIL_REFLECTION = -0.85` as one working anatomy — `defaultVoiceAnatomyProfile()` below reuses those exact proven values as the default rather than inventing new ones.",
    "exports": [
      {
        "name": "createVoiceAnatomyProfile",
        "kind": "function",
        "signature": "createVoiceAnatomyProfile({ oralAreas, nasalAreas = null, glottalReflection = DEFAULT_GLOTTAL_REFLECTION, lipReflection = DEFAULT_LIP_REFLECTION, nostrilReflection = DEFAULT_NOSTRIL_REFLECTION, formantScale = 1, pitchRangeHz = { min: 80, max: 400 }, } = {})",
        "summary": ""
      },
      {
        "name": "defaultVoiceAnatomyProfile",
        "kind": "function",
        "signature": "defaultVoiceAnatomyProfile()",
        "summary": "The R6/R6A-proven default anatomy (uniform tube, no nasal coupling) — a reasonable starting profile for an authored (non-cloned) character, not a claim of acoustic realism."
      },
      {
        "name": "DEFAULT_NUM_ORAL_SECTIONS",
        "kind": "constant",
        "signature": "DEFAULT_NUM_ORAL_SECTIONS",
        "summary": "VoiceAnatomyProfile.js — Phase 2 Voice Box state schema (`v0`). `ARCHITECTURE.md` §4's `CharacterAnatomyState`: \"persistent per-character morphology (equivalent role to `PRVC-v0`'s `formantScale`/pitch-range calibration, but for non-cloned/authored characters rather than captured voices)\". This is the STATIC per-voice data that stays constant across frames — the rest-position oral tube area profile, optional nasal branch area profile, and the three boundary reflection coefficients `nn/kernels/articulatory/tract_waveguide.js`/`nasal_junction.js` require (`glottalReflection`/`lipReflection`/`nostrilReflection`, all magnitude `< 1` for BIBO stability per `risk/KellyLochbaumWaveguide.js` (R6)) — as distinct from `NeuralPhysiologyState`'s `TractGestureHead`, which is the per-FRAME predicted deviation/gesture applied on top of this rest profile, not a replacement for it. `risk/VoiceBoxWaveguideProbe.js` (R6/R6A) already proved out `NUM_SECTIONS = 32`/`NASAL_SECTIONS = 16`, `GLOTTAL_REFLECTION = 0.85`, `LIP_REFLECTION = -0.9`, `NOSTRIL_REFLECTION = -0.85` as one working anatomy — `defaultVoiceAnatomyProfile()` below reuses those exact proven values as the default rather than inventing new ones."
      },
      {
        "name": "DEFAULT_NUM_NASAL_SECTIONS",
        "kind": "constant",
        "signature": "DEFAULT_NUM_NASAL_SECTIONS",
        "summary": ""
      },
      {
        "name": "DEFAULT_GLOTTAL_REFLECTION",
        "kind": "constant",
        "signature": "DEFAULT_GLOTTAL_REFLECTION",
        "summary": ""
      },
      {
        "name": "DEFAULT_LIP_REFLECTION",
        "kind": "constant",
        "signature": "DEFAULT_LIP_REFLECTION",
        "summary": ""
      },
      {
        "name": "DEFAULT_NOSTRIL_REFLECTION",
        "kind": "constant",
        "signature": "DEFAULT_NOSTRIL_REFLECTION",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/articulatory/WaveguideAcousticState",
    "path": "agi/reference/particle_voice/articulatory/WaveguideAcousticState.md",
    "source": "agi/particle_voice/articulatory/WaveguideAcousticState.js",
    "import": "/agi/particle_voice/articulatory/WaveguideAcousticState.js",
    "sourceHash": "448316a5a8989253150f963573bbfa72eada9c9598dc53a4baa42e5768d48772",
    "summary": "WaveguideAcousticState.js — Phase 2 Voice Box state schema (`v0`). \"The R6 `KellyLochbaumWaveguide.js` tract's per-sample output (radiated signal + nasal tap)\" per `ARCHITECTURE.md` §4 — the JS-side timestamped container for one dispatched chunk's worth of `nn/kernels/articulatory/tract_waveguide.js` (`radiated`, plus the persisted `finalRight`/`finalLeft`/`finalPrevOutput` continuation state that kernel's packed output layout produces) and, optionally, a coupled `nn/kernels/articulatory/nasal_junction.js` chunk's own radiated output. This container does not itself dispatch either kernel or compute `nasalTapSignal` (`tract_waveguide.js`'s own docstring notes the nasal tap read-out is still deferred there) — it only fixes the shape a caller that DOES dispatch those kernels should hand back, so downstream consumers (the audio bridge, `physiology_feedback_reduce.js`, `visual_state_decimate.js`) have one stable state shape to depend on regardless of which kernel(s) produced it.",
    "exports": [
      {
        "name": "createWaveguideAcousticState",
        "kind": "function",
        "signature": "createWaveguideAcousticState({ sampleIndex, numSamples, radiated, finalRight, finalLeft, finalPrevOutput, nasalRadiated = null })",
        "summary": ""
      },
      {
        "name": "nextChunkInitialState",
        "kind": "function",
        "signature": "nextChunkInitialState(previousState)",
        "summary": "Builds the next chunk's initial-state inputs (`tract_waveguide.js`'s `initial_right`/`initial_left`/`initial_prev_output` bindings) from a previous chunk's `WaveguideAcousticState` — the exact cross-chunk continuity `tests/particle-voice/phase2-dsp.html`'s `tract_waveguide` test verifies at the kernel level."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/frontend/G2PModel",
    "path": "agi/reference/particle_voice/frontend/G2PModel.md",
    "source": "agi/particle_voice/frontend/G2PModel.js",
    "import": "/agi/particle_voice/frontend/G2PModel.js",
    "sourceHash": "c7cc69895f4d227ad78224baa6ece1a568c91edad1c547a17b2b00763ea9673e",
    "summary": "G2PModel.js — Phase 3 ParticleVoice text frontend. Grapheme-to-phoneme fallback for words `PronunciationLexicon.js` does not cover, plus `textToPhonemeSequence()` — the deterministic lowering `spec/VoicePlan-v0.md` §4 describes, which turns raw text into the `{ phonemeIds, stress, punctuationContext }` triple the model consumes. ## It is rules, not a network — deliberately The plan calls for a \"minimal `G2PModel.js`\" in Phase 3, and this is a **deterministic, context-sensitive letter-to-sound rule set**, not a trained model. That is the honest choice at this stage: a neural G2P needs a pronunciation corpus to train against, and this project vendors no external data (CMUdict included). Rules give reproducible output today, and the class boundary here is what a learned model would later slot into without touching callers. The name matches the plan's; the implementation does not pretend to be learned. ## Accuracy expectations, stated plainly English orthography is not a function of spelling alone, so a rule set of this size gets the regular cases right and a meaningful minority wrong (`read` has two pronunciations; `bread` breaks the `ea → IY` rule; stress placement in long words is genuinely lexical). That is exactly why `PronunciationLexicon.js` exists and is consulted FIRST — every word whose spelling actively lies belongs there. G2P's job is to make unseen words (character names, invented words, rare vocabulary) pronounceable and stable, not to be a dictionary. ## Rule format Each rule is `[leftContext, target, rightContext, phonemes]`, tried in order at each position, first match winning. Rules are authored longest-target-first per letter so digraphs beat single letters. The context pattern language is deliberately tiny — three metacharacters, no regex — so a rule's behaviour is obvious by inspection: `#` word boundary (start of a left context, end of a right context) `C` exactly one consonant letter `V` exactly one vowel letter anything else, a literal letter Stress is assigned afterwards by syllable heuristic (see `assignStressHeuristic`), because stress is a property of the whole word, not of any single grapheme.",
    "exports": [
      {
        "name": "G2PModel",
        "kind": "class",
        "signature": "class G2PModel",
        "summary": ""
      },
      {
        "name": "graphemesToPhonemeSymbols",
        "kind": "function",
        "signature": "graphemesToPhonemeSymbols(word)",
        "summary": "Apply the rule set to a spelling."
      },
      {
        "name": "assignStressHeuristic",
        "kind": "function",
        "signature": "assignStressHeuristic(word, symbols)",
        "summary": "Place a single primary stress by syllable heuristic. Documented as a heuristic because English stress is genuinely lexical — the lexicon is the place to be exact. - Suffixes `-tion/-sion/-cial/-tial/-cian` pull stress onto the syllable immediately before them (na-TION-al → \"NA-tion\", in-for-MA-tion). - `-ity/-ety/-ify/-ogy/-omy` pull stress two syllables back. - Otherwise stress the first syllable, unless the word opens with a normally-unstressed prefix and has more than one syllable, in which case stress the second."
      },
      {
        "name": "graphemesToPhonemes",
        "kind": "function",
        "signature": "graphemesToPhonemes(word)",
        "summary": ""
      },
      {
        "name": "textToPhonemeSequence",
        "kind": "function",
        "signature": "textToPhonemeSequence(normalized, { g2p = new G2PModel()",
        "summary": "Deterministic lowering of normalized text into `spec/VoicePlan-v0.md` §4's `PhonemeSequence`. All three arrays are the same length, one entry per phoneme instance including structural tokens, exactly as that spec requires. Structural insertion follows `spec/PhonemeSet-v0.md` §4's Effect column: a phrase's `structural` token (`SP` for comma-level, `SIL` for terminals) is appended after its words, and a paragraph boundary additionally emits `BREATH` before its `SIL` (§4: \"longer SIL, breath event likely\")."
      },
      {
        "name": "createG2PModel",
        "kind": "function",
        "signature": "createG2PModel(options)",
        "summary": ""
      },
      {
        "name": "LTS_RULES",
        "kind": "constant",
        "signature": "LTS_RULES",
        "summary": "Letter-to-sound rules, grouped by first letter of `target`. Order within a group IS the priority order."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/frontend/PhonemeSet",
    "path": "agi/reference/particle_voice/frontend/PhonemeSet.md",
    "source": "agi/particle_voice/frontend/PhonemeSet.js",
    "import": "/agi/particle_voice/frontend/PhonemeSet.js",
    "sourceHash": "7d7c3c56c4e63354c33e516a24aa3efded914b60af82ec5adeac4e5c9c1dd821",
    "summary": "PhonemeSet.js — Phase 3 ParticleVoice text frontend. The executable form of `spec/PhonemeSet-v0.md`: the English phoneme inventory, its per-entry articulatory feature vectors, the stress scale, and the punctuation-context tags. This module is the SINGLE SOURCE OF TRUTH every other Phase 3 component reads — `PronunciationLexicon.js` validates against it, `G2PModel.js` targets it, `PhonemeEncoder.js` sizes its embedding table from `PHONEME_COUNT`, and `ArticulationHead.js` consumes the feature vectors to bias `NeuralPhysiologyState`. ## Corrections to `spec/PhonemeSet-v0.md` Encoding the spec surfaced four places where its §1 tables (the actual data) disagree with its §1 summary line or its §2 enum declarations. The tables are treated as normative and the spec has been corrected to match; each is recorded here because a reader comparing the two must know which way the discrepancy was resolved. 1. **Inventory size.** §1 claimed \"24 vowel symbols + 24 consonant symbols + 3 structural tokens = 51\" and `u8` IDs `0..50`. The tables actually list **10 monophthongs + 5 diphthongs = 15 vowels**, so the real total is **42** entries, IDs `0..41`. \"24 vowel symbols\" appears to be an accidental duplication of the consonant count. This matters concretely: it sets `PhonemeEncoder.js`'s embedding table size and the legal `phonemeIds` range in `spec/VoicePlan-v0.md` §4, both of which were also stated as 51/`0..50` and are now corrected. 2. **Vowel height values.** §2 declares `height: low | mid | high`, but §1's table also uses `low-mid` (`AO`) and `high-mid` (`UH`). All five values are kept (see `HEIGHT`); the narrower §2 enum was the error. 3. **`ER`'s rounded column** contains `rhotic`, which is not a rounded-ness value at all. Modeled here as `rounded: false` plus a separate `rhotic: true` flag, since rhoticity is an independent articulatory property (it shapes the area profile rather than the lip aperture). 4. **`W`'s place** is `bilabial-velar` in §1's table, which is not one of §2's `place` values. Modeled as `place: 'bilabial'` with `secondaryPlace: 'velar'`, preserving the information without inventing an enum member the spec does not define. Structural tokens (`SIL`/`SP`/`BREATH`) carry `place: null`, `manner: null`, `voiced: false` and are identified by `category === 'structural'` — deliberately NOT given invented enum values, since §2's `place`/`manner` enums describe articulated speech sounds only.",
    "exports": [
      {
        "name": "phonemeIdOf",
        "kind": "function",
        "signature": "phonemeIdOf(symbol)",
        "summary": ""
      },
      {
        "name": "hasPhoneme",
        "kind": "function",
        "signature": "hasPhoneme(symbol)",
        "summary": ""
      },
      {
        "name": "phonemeAt",
        "kind": "function",
        "signature": "phonemeAt(id)",
        "summary": ""
      },
      {
        "name": "phonemeBySymbol",
        "kind": "function",
        "signature": "phonemeBySymbol(symbol)",
        "summary": ""
      },
      {
        "name": "diphthongTargetIds",
        "kind": "function",
        "signature": "diphthongTargetIds(id)",
        "summary": ""
      },
      {
        "name": "constrictionPositionOf",
        "kind": "function",
        "signature": "constrictionPositionOf(id)",
        "summary": ""
      },
      {
        "name": "isValidStress",
        "kind": "function",
        "signature": "isValidStress(stress)",
        "summary": ""
      },
      {
        "name": "isValidPunctuationContext",
        "kind": "function",
        "signature": "isValidPunctuationContext(tag)",
        "summary": ""
      },
      {
        "name": "isVowel",
        "kind": "function",
        "signature": "isVowel(id)",
        "summary": ""
      },
      {
        "name": "isConsonant",
        "kind": "function",
        "signature": "isConsonant(id)",
        "summary": ""
      },
      {
        "name": "isStructural",
        "kind": "function",
        "signature": "isStructural(id)",
        "summary": ""
      },
      {
        "name": "isVoiced",
        "kind": "function",
        "signature": "isVoiced(id)",
        "summary": ""
      },
      {
        "name": "isDiphthong",
        "kind": "function",
        "signature": "isDiphthong(id)",
        "summary": ""
      },
      {
        "name": "STRESS",
        "kind": "constant",
        "signature": "STRESS",
        "summary": "§3: per-vowel-instance stress, separate from the phoneme symbol."
      },
      {
        "name": "PUNCTUATION_CONTEXT",
        "kind": "constant",
        "signature": "PUNCTUATION_CONTEXT",
        "summary": "§4: punctuation-context tag ids, emitted per phoneme by `TextNormalizer.js`."
      },
      {
        "name": "PLACE",
        "kind": "constant",
        "signature": "PLACE",
        "summary": ""
      },
      {
        "name": "MANNER",
        "kind": "constant",
        "signature": "MANNER",
        "summary": ""
      },
      {
        "name": "HEIGHT",
        "kind": "constant",
        "signature": "HEIGHT",
        "summary": "Includes §1's `low-mid`/`high-mid`, which §2's three-value enum omitted (correction 2)."
      },
      {
        "name": "BACKNESS",
        "kind": "constant",
        "signature": "BACKNESS",
        "summary": ""
      },
      {
        "name": "PLACE_POSITION",
        "kind": "constant",
        "signature": "PLACE_POSITION",
        "summary": "§2: place → normalized position (0 = glottis, 1 = lips) of the narrowest point, fed to `shapedAreaProfile`'s control points. Provisional estimates per the spec's own §6 — NOT measured against vocal-tract MRI data."
      },
      {
        "name": "PHONEMES",
        "kind": "constant",
        "signature": "PHONEMES",
        "summary": "The inventory, in `spec/PhonemeSet-v0.md` §1 table order. **Array index IS the `u8` phoneme id** — appending is safe, reordering is a breaking change to every trained embedding table."
      },
      {
        "name": "PHONEME_COUNT",
        "kind": "constant",
        "signature": "PHONEME_COUNT",
        "summary": "42 — sizes `PhonemeEncoder.js`'s embedding table. See correction 1."
      },
      {
        "name": "MAX_PHONEME_ID",
        "kind": "constant",
        "signature": "MAX_PHONEME_ID",
        "summary": "Highest legal `u8` phoneme id (41)."
      },
      {
        "name": "STRUCTURAL_SYMBOLS",
        "kind": "constant",
        "signature": "STRUCTURAL_SYMBOLS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/frontend/PronunciationLexicon",
    "path": "agi/reference/particle_voice/frontend/PronunciationLexicon.md",
    "source": "agi/particle_voice/frontend/PronunciationLexicon.js",
    "import": "/agi/particle_voice/frontend/PronunciationLexicon.js",
    "sourceHash": "b99b1bb0da549f99d195ede8f5a035789cc49a13f03e37ebe170c254bfcd4276",
    "summary": "PronunciationLexicon.js — Phase 3 ParticleVoice text frontend. Word → phoneme-sequence dictionary, checked entry-by-entry against `PhonemeSet.js` at construction time. Sits between `TextNormalizer.js` and `G2PModel.js`: a lexicon hit is authoritative, and only words with no entry fall through to G2P's letter-to-sound rules. ## ⚠ Stress digits are the INVERSE of CMUdict Pronunciation strings use CMUdict's familiar `SYMBOL+digit` shape (`HH AH L OW2`), but the digit follows `spec/PhonemeSet-v0.md` §3: **0 = unstressed, 1 = SECONDARY, 2 = PRIMARY** CMUdict is the other way round (`1` = primary, `2` = secondary). Copying CMUdict strings verbatim would therefore silently swap primary and secondary stress on every polysyllabic word — audible as wrong prosody, not as an error. This module cannot detect that mistake for you, so the convention is stated here, in `spec/PhonemeSet-v0.md` §3, and enforced by `MAX_STRESS`-range validation only. ## Authoring rules (validated, not merely documented) - Every symbol must exist in `PhonemeSet.js`. A typo throws at construction, naming the word — never at synthesis time. - A stress digit is legal ONLY on a vowel. Spec §3 defines stress as a per-vowel-instance property, so `S2` is rejected outright rather than quietly ignored. - Omitting the digit on a vowel means unstressed (0). This keeps reduced function words (`the` = `DH AH`) readable. - **At most one** primary stress per word. Not *exactly* one: genuinely reduced function words (`the`, `a`, `of`) carry no stress at all, and forcing a primary onto them would produce robotically over-stressed speech. ## Scope `CORE_LEXICON` is a deliberately curated core, NOT a full dictionary. It covers high-frequency function/content words plus — the reason a lexicon exists at all — words whose spelling actively lies about their pronunciation (`one`, `through`, `women`, `said`, `colonel`, `choir`, `island`), which any letter-to-sound rule set gets wrong. CMUdict is not vendored: this project takes no external data dependencies, and the long tail is `G2PModel.js`'s job. Callers add domain vocabulary (character names, place names) via `add()`/`addAll()`.",
    "exports": [
      {
        "name": "PronunciationLexicon",
        "kind": "class",
        "signature": "class PronunciationLexicon",
        "summary": ""
      },
      {
        "name": "parsePronunciation",
        "kind": "function",
        "signature": "parsePronunciation(pronunciation, wordLabel = '<unknown>')",
        "summary": "Parse one `SYMBOL+digit` pronunciation string."
      },
      {
        "name": "createPronunciationLexicon",
        "kind": "function",
        "signature": "createPronunciationLexicon(options)",
        "summary": ""
      },
      {
        "name": "MAX_STRESS",
        "kind": "constant",
        "signature": "MAX_STRESS",
        "summary": ""
      },
      {
        "name": "LETTER_NAMES",
        "kind": "constant",
        "signature": "LETTER_NAMES",
        "summary": "Spoken letter names, for spelling out initialisms. `TextNormalizer.js` flags all-caps tokens with `wasAllCaps` but deliberately does not decide whether they are words or initialisms; `spellOut()` here is the mechanism for the latter."
      },
      {
        "name": "CORE_LEXICON",
        "kind": "constant",
        "signature": "CORE_LEXICON",
        "summary": "Curated core lexicon. Stress digits: 2 = PRIMARY (see the warning above). Grouped by why each entry is here."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/frontend/TextNormalizer",
    "path": "agi/reference/particle_voice/frontend/TextNormalizer.md",
    "source": "agi/particle_voice/frontend/TextNormalizer.js",
    "import": "/agi/particle_voice/frontend/TextNormalizer.js",
    "sourceHash": "ddc390177f2ebb15055b52bc90e4b1f6bebf549283dafb49299720ab4ab60cbc",
    "summary": "TextNormalizer.js — Phase 3 ParticleVoice text frontend. Turns raw author/LLM text into speakable words plus the per-phrase punctuation-context tags `spec/PhonemeSet-v0.md` §4 defines, ready for `PronunciationLexicon.js`/`G2PModel.js` to lower into phoneme ids. Output shape (`normalizeText`): { raw: string, phrases: [{ words: [{ text, wasAllCaps }], boundary: PUNCTUATION_CONTEXT id,   // §4 tag for THIS phrase's end structural: 'SP' | 'SIL' | null,    // token to insert at the boundary breathLikely: boolean,              // §4: paragraph breaks only }] } One tag per phrase (not per phoneme): `spec/VoicePlan-v0.md` §4's per-phoneme `punctuationContext` array is produced downstream by repeating a phrase's tag across the phonemes its words expand into, which is exactly what that spec means by \"repeats within a phrase, changes at boundaries\". ## Ordering matters Abbreviation expansion and decimal-point protection run BEFORE phrase splitting, because both consume `.` characters that would otherwise be mistaken for sentence terminators — `Dr. Smith` is one phrase, not two, and `3.14` is one number, not `3` then `14`. Getting this order wrong is the classic text-frontend bug, so it is enforced by construction here rather than left to a later cleanup pass. ## Deliberate non-guesses Two things this module refuses to infer, because guessing wrong is worse than not trying: - **Years.** `1999` could be a year (\"nineteen ninety nine\") or a quantity (\"one thousand nine hundred ninety nine\"), and `1500 dollars` proves the quantity reading is often right. Bare integers are ALWAYS read as cardinals; `expandYear()` is exported for callers that actually know the context (e.g. a date field), rather than being applied by a heuristic that would silently corrupt prices and counts. - **Acronym pronunciation.** `NASA` is said as a word, `USA` letter by letter, and nothing in the surface form distinguishes them. All-caps tokens are passed through with `wasAllCaps: true` so `PronunciationLexicon.js` gets first refusal and `G2PModel.js` can fall back to spelling out — the decision belongs where the pronunciation knowledge lives, not here. Dates and clock times are likewise out of scope for `v0` (both are format-ambiguous: `3/4` is a date in one locale and a fraction in another). They are documented as unhandled rather than half-handled.",
    "exports": [
      {
        "name": "expandCardinal",
        "kind": "function",
        "signature": "expandCardinal(n)",
        "summary": ""
      },
      {
        "name": "expandOrdinal",
        "kind": "function",
        "signature": "expandOrdinal(n)",
        "summary": ""
      },
      {
        "name": "expandDigits",
        "kind": "function",
        "signature": "expandDigits(digits)",
        "summary": ""
      },
      {
        "name": "expandYear",
        "kind": "function",
        "signature": "expandYear(year)",
        "summary": "Read a 4-digit year the way speech does. NOT applied automatically — see this module's \"deliberate non-guesses\" note; callers that know a number is a year opt in explicitly."
      },
      {
        "name": "normalizeCharacters",
        "kind": "function",
        "signature": "normalizeCharacters(raw)",
        "summary": "Unicode/typography normalization: NFKC, curly quotes and dashes to ASCII, whitespace collapsed (paragraph breaks preserved as `\\n\\n`)."
      },
      {
        "name": "normalizeText",
        "kind": "function",
        "signature": "normalizeText(raw)",
        "summary": ""
      },
      {
        "name": "flattenWords",
        "kind": "function",
        "signature": "flattenWords(normalized)",
        "summary": ""
      },
      {
        "name": "MAX_CARDINAL",
        "kind": "constant",
        "signature": "MAX_CARDINAL",
        "summary": ""
      },
      {
        "name": "ABBREVIATIONS",
        "kind": "constant",
        "signature": "ABBREVIATIONS",
        "summary": "Expanded before phrase splitting, so their trailing `.` never reads as a sentence end. Keys are matched case-insensitively without the period."
      },
      {
        "name": "SYMBOL_WORDS",
        "kind": "constant",
        "signature": "SYMBOL_WORDS",
        "summary": "Standalone symbol → word. Applied per token, so `&` between words works but `AT&T` is left to the lexicon."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/lab/ManualArticulator",
    "path": "agi/reference/particle_voice/lab/ManualArticulator.md",
    "source": "agi/particle_voice/lab/ManualArticulator.js",
    "import": "/agi/particle_voice/lab/ManualArticulator.js",
    "sourceHash": "e2aa28a20c38cb338c70ec14130e5c7f5142eddc816eb2df922d5bed3b07c73c",
    "summary": "ManualArticulator.js — Phase 3 ParticleVoice lab. Turns direct articulator controls (the Voice Box demo's sliders) into the SAME physiology-stream shape `ArticulationHead.predict()` produces, so `ParticleVoiceModel.renderPhysiology()` accepts it unchanged. ## Why this is a module and not inline demo code The plan requires the live demo to have \"manual articulator mode + neural mode; same `VisualAirflowState`\". If manual mode built its own control path — or worse, called `ParticleTract` directly — the two modes would diverge: different smoothing, different validation, a different visualization source. By emitting the identical stream shape, manual mode goes through exactly the same `renderPhysiology()` → `ParticleTract` → `SafetyLimiter` path, and the visualization comes from the same solver snapshots. That makes \"same VisualAirflowState\" structural rather than a promise, and it means this logic is testable headlessly instead of only by clicking. ## Controls Deliberately the physical knobs, not phonemes — the point of manual mode is to drive the tract directly: - `f0Hz`, `voiced` — glottal source - `constrictionPosition` (0 = glottis, 1 = lips) and `constrictionArea` — one moving constriction, the single most expressive articulator - `pharynxArea`, `lipArea` — the tube's two ends - `nasalCoupling` — velum - `fricationAmplitude` — turbulence at the constriction - `pressure` — subglottal drive A held pose is rendered as a constant stream; `interpolatePoses()` produces a glide between two poses, which is how the demo can sweep a diphthong or a closure/release gesture without hand-authoring every frame.",
    "exports": [
      {
        "name": "ManualArticulator",
        "kind": "class",
        "signature": "class ManualArticulator",
        "summary": ""
      },
      {
        "name": "normalizePose",
        "kind": "function",
        "signature": "normalizePose(pose = {})",
        "summary": "Validate and clamp a partial pose into a complete one."
      },
      {
        "name": "interpolatePoses",
        "kind": "function",
        "signature": "interpolatePoses(a, b, t)",
        "summary": "Linear blend between two poses; `voiced` switches at the midpoint since it is boolean."
      },
      {
        "name": "poseAreaProfile",
        "kind": "function",
        "signature": "poseAreaProfile(pose, numSections)",
        "summary": "Area profile for one pose, using the same 3-control-point `shapedAreaProfile` shape `ArticulationHead` uses for vowels."
      },
      {
        "name": "createManualArticulator",
        "kind": "function",
        "signature": "createManualArticulator(options)",
        "summary": ""
      },
      {
        "name": "DEFAULT_POSE",
        "kind": "constant",
        "signature": "DEFAULT_POSE",
        "summary": ""
      },
      {
        "name": "POSE_CONTROLS",
        "kind": "constant",
        "signature": "POSE_CONTROLS",
        "summary": "Slider metadata for the demo UI, kept beside the pose it describes so the two cannot drift apart."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/lab/TractSagittalView",
    "path": "agi/reference/particle_voice/lab/TractSagittalView.md",
    "source": "agi/particle_voice/lab/TractSagittalView.js",
    "import": "/agi/particle_voice/lab/TractSagittalView.js",
    "sourceHash": "942497f48f9c80bea8834b00e8d7d25aed300f421077e4fd8efcb6f4a1605cb4",
    "summary": "TractSagittalView.js — mid-sagittal renderer for the vocal tract area function. Draws what the solver is actually doing, as an anatomical cross-section: the pharynx rising from the glottis, the bend behind the tongue, the oral cavity, the teeth and the lips. The tract's `areas` array IS the picture — section `i` is a slice of tube, and its cross-sectional area is the gap between the tongue surface and the roof of the mouth at that point. ## Why a sagittal view rather than a plot of the numbers A line plot of 32 areas is readable but not *diagnosable*: it does not tell you whether a shape is a plausible mouth. Errors in this project have repeatedly been of exactly that kind — a tract twice the correct length, a \"stop\" whose closure never closed, a constriction so tight it would whistle. All of those are obvious at a glance in an anatomical view and invisible in a spectrum. ## Geometry The centreline follows an arc, matching how the real tract bends ~90 degrees behind the tongue: the glottis end points up, the lip end points forward. Each section is drawn radially across that arc, with the OUTER boundary fixed (the rigid palate, teeth and rear pharyngeal wall) and the INNER boundary displaced by the section's diameter (the tongue and jaw, which are what move). Diameter, not area, sets the drawn width: a tube's diameter goes as the SQUARE ROOT of its area, so plotting area directly would exaggerate open sections and make near-closures look tighter than they are.",
    "exports": [
      {
        "name": "TractSagittalView",
        "kind": "class",
        "signature": "class TractSagittalView",
        "summary": "Renders one frame of tract geometry. Stateless by design: it takes the geometry and draws it, so the same view can be driven by `ArticulationHead`'s per-frame stream, by `ManualArticulator`'s live sliders, or by a single static profile from `FormantSolver`, with no notion of playback inside it."
      },
      {
        "name": "diameterForArea",
        "kind": "function",
        "signature": "diameterForArea(areaCm2)",
        "summary": ""
      },
      {
        "name": "LANDMARKS",
        "kind": "constant",
        "signature": "LANDMARKS",
        "summary": "Anatomical landmarks as a fraction of tract length from the glottis, used only for labels and the teeth marker."
      },
      {
        "name": "DEFAULT_VIEW_OPTIONS",
        "kind": "constant",
        "signature": "DEFAULT_VIEW_OPTIONS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/model/ArticulationHead",
    "path": "agi/reference/particle_voice/model/ArticulationHead.md",
    "source": "agi/particle_voice/model/ArticulationHead.js",
    "import": "/agi/particle_voice/model/ArticulationHead.js",
    "sourceHash": "2edfa30adfbaba9b6e993d08a1381f7a4bf43d21cc13de20b14dc0fcb9442b33",
    "summary": "ArticulationHead.js — Phase 3 ParticleVoice model. Per-frame streams → `NeuralPhysiologyState` (`articulatory/NeuralPhysiologyState.js`): the four control heads plus uncertainty that `ParticleTract` actually consumes. This is where `spec/PhonemeSet-v0.md` §2's articulatory feature vectors finally reach the vocal tract — place becomes a constriction position, manner becomes a constriction degree, height/backness/rounded become a vowel area profile. ## Rule-based, mapping features to geometry Like `ProsodyPlanner`, this is a deterministic feature→geometry mapping, not a trained head. `spec/PhonemeSet-v0.md` §6 already flags its place-position percentages as \"provisional estimates ... not measured against real vocal-tract MRI data\", and the vowel profiles here are the same kind of estimate: a 3-control-point `shapedAreaProfile` whose constriction position comes from backness, constriction area from height, and lip area from rounding. They are ordered correctly (high vowels narrower than low, front constrictions further forward than back) and produce distinguishable formants — verified in Phase 2 to be what makes a vowel a vowel rather than a buzz — but they are not fitted to measured data. ## Two constraints that are NOT stylistic 1. **`ee` must be strictly positive, even when unvoiced.** It is tempting to set the glottal excitation to 0 on a voiceless frame. That breaks `glottal_lf.js`: its Newton solve for `alpha` computes `areaPrime = (-ee / sin(wg*te)) * (...)`, so `ee = 0` makes the derivative exactly 0 and the update `alpha -= (area - target) / 0` evaluates to **NaN**, which then propagates through the whole waveguide. `NeuralPhysiologyState`'s own validator independently requires `ee > 0`. Voiceless frames therefore use a small positive `UNVOICED_EE` — inaudible, but numerically safe. 2. **Areas are SMOOTHED across frames, per-manner.** Snapping the area profile to a new target at a phoneme boundary is a step change in the waveguide's reflection coefficients, which radiates as a click — the precise artifact Phase 2's exit gate spent effort eliminating. Each manner gets its own time constant because the right smoothing speed is phonetically real, not a tuning knob: a stop's closure and release ARE fast (smearing them destroys the plosive), while a vowel-to-vowel transition is genuinely slow. A single global constant would either smear plosives or click on vowels. ## Output shape `predict()` returns COMPACT typed arrays (one row per frame), not an array of frozen `NeuralPhysiologyState` objects: a few seconds of speech is hundreds of frames, and materializing hundreds of frozen nested objects per utterance would be wasteful when the consumer reads them once, in order. `physiologyStateAt(result, frame)` builds the real validated schema object on demand, so the schema is genuinely exercised (and validated) rather than bypassed.",
    "exports": [
      {
        "name": "ArticulationHead",
        "kind": "class",
        "signature": "class ArticulationHead",
        "summary": ""
      },
      {
        "name": "physiologyStateAt",
        "kind": "function",
        "signature": "physiologyStateAt(result, frame, { glottalReflection, lipReflection } = {})",
        "summary": "Materialize one frame of a `predict()` result as a validated `NeuralPhysiologyState`. Built on demand rather than eagerly — see the header's \"output shape\" note — so the schema and its validators are genuinely exercised without allocating hundreds of frozen objects per utterance."
      },
      {
        "name": "createArticulationHead",
        "kind": "function",
        "signature": "createArticulationHead(config)",
        "summary": ""
      },
      {
        "name": "UNVOICED_EE",
        "kind": "constant",
        "signature": "UNVOICED_EE",
        "summary": "Small but strictly positive glottal excitation for voiceless frames. See constraint 1 in the header: 0 would make `glottal_lf.js`'s Newton solve produce NaN."
      },
      {
        "name": "BACKNESS_POSITION",
        "kind": "constant",
        "signature": "BACKNESS_POSITION",
        "summary": "Fallback constriction position (0 = glottis, 1 = lips) by vowel backness, and cross-section by height. These are NO LONGER the primary path for the ten monophthongs — see `vowelProfile()`. They were an articulatory-adjective mapping (\"front vowels constrict further forward\", \"high vowels constrict more tightly\") that is directionally right but produces whatever formants it happens to produce. Since vowel identity is carried almost entirely by F1/F2, \"plausible geometry\" is not good enough: uncalibrated profiles pull every vowel toward a neutral timbre, which is heard as speech-like noise with no decipherable vowels. Retained only as the fallback for any vowel with no entry in `VOWEL_AREA_PARAMS`, so adding a phoneme to `PhonemeSet` cannot crash synthesis before it has been calibrated."
      },
      {
        "name": "HEIGHT_AREA",
        "kind": "constant",
        "signature": "HEIGHT_AREA",
        "summary": ""
      },
      {
        "name": "MANNER_WIDTH",
        "kind": "constant",
        "signature": "MANNER_WIDTH",
        "summary": "Constriction LENGTH by manner, as a fraction of tract length. A constriction specified as a single control point does not survive `shapedAreaProfile`'s linear interpolation: the profile tapers to the target only exactly AT that point, and since the point rarely lands on a section centre, the realised minimum is far wider than requested. Measured, a stop asking for 0.02 cm^2 actually reached **0.138** — nearly 7x too open. That is not a cosmetic error: a stop whose closure never closes produces neither the silence nor the release burst that identify it, and stops are six of the most frequent consonants in English. The effect got worse when the section count was corrected from 32 to 16, because coarser sections smear a point further. Giving the constriction a length makes it span whole sections, so it genuinely reaches the target area. The values are anatomical: a stop's occlusion covers roughly 1.5-2 cm of the tract, a fricative channel is comparable, and an approximant's narrowing is longer and gentler. At 17 cm / 16 sections one section is 1.06 cm, so 0.10 (1.7 cm) is about 1.6 sections — enough to hold the target rather than merely touch it."
      },
      {
        "name": "MANNER_AREA",
        "kind": "constant",
        "signature": "MANNER_AREA",
        "summary": "Constriction cross-section by consonant manner — the \"degree\" half of spec §2's place/manner pair."
      },
      {
        "name": "VOICING_TIME_MS",
        "kind": "constant",
        "signature": "VOICING_TIME_MS",
        "summary": "Smoothing time constant for the glottal excitation amplitude. `ee` switches between `voicedEe` (1.0) and `UNVOICED_EE` (1e-3) — a factor of a THOUSAND, i.e. a 60 dB step. Applied instantaneously that is a hard click at every voicing transition, and an English sentence has one at every voiceless consonant; several per second turns into a sputter that swamps the speech. Real vocal folds cannot change amplitude discontinuously either, so smoothing here is physical as well as necessary. ~18 ms is fast enough to keep a voiceless stop sounding unvoiced (its closure alone is ~25 ms) while removing the step."
      },
      {
        "name": "GESTURE_TIME_MS",
        "kind": "constant",
        "signature": "GESTURE_TIME_MS",
        "summary": "Area-smoothing time constants in ms, by manner — the speed of the ARTICULATOR, nothing else. ## These were far too slow, and every stop in every utterance was a glide The previous values (stop 25 ms, vowel 50 ms) were justified by the claim that \"a stop at 25 ms still reaches ~94% of its closure within a typical 70 ms stop, so the plosive is preserved\". That reasoning is wrong, and wrong in a way worth recording: it measures the PERCENTAGE OF DISTANCE travelled, but what decides whether a stop is a stop is the ABSOLUTE area reached. Going 94% of the way from a vowel's ~3.0 cm^2 to a 0.02 cm^2 closure leaves **0.18 cm^2** — which is not a closure at all, it is a narrow approximant. So the tract never actually blocked, no stop ever produced silence or a release burst, and all six English stops came out as vowel-like glides. Perceptually the whole utterance collapses toward a string of semivowels, which is exactly the reported \"yeah yeah yeah\". For a target near zero, percentage-of-distance and absolute-value diverge arbitrarily far. This is the same relative-versus-absolute confusion that produced the earlier bad thresholds in this project. ## Why they can now be as fast as the phonetics actually requires These constants USED to be doing two jobs: modelling articulator inertia AND preventing the per-chunk reflection-coefficient step from clicking. The second job is gone — `tract_waveguide.js` now interpolates `junction_k` per sample, so geometry is continuous no matter how fast the frame targets move. That frees these to be purely phonetic. O'Connor et al. (1957) found listeners hear a formant transition as a STOP below ~50 ms and as a SEMIVOWEL above it, so a stop's gesture must complete well inside 50 ms. At the 10 ms frame interval, tau = 8 ms gives alpha 0.71, reaching 0.026 cm^2 (a real closure) within 50 ms. Approximants stay slower: the same literature has /w j r l/ holding their formant loci for 30-50 ms, i.e. genuinely gliding rather than snapping."
      },
      {
        "name": "RELEASE_AREA_CM2",
        "kind": "constant",
        "signature": "RELEASE_AREA_CM2",
        "summary": "Stop RELEASE BURST. A plosive is two acoustic events, not one: a silent closure, then a sharp transient when the occlusion opens and the pressure built up behind it escapes. Only the closure was being modelled, so stops had the right silence and then simply faded back into the next vowel. The burst is a primary place cue — much of what distinguishes /p/ from /t/ from /k/ lives in its spectrum — so without it stops are detectable but not identifiable. Pink Trombone models the same thing, adding a transient whenever a diameter goes from zero to positive; this is the equivalent, expressed through the turbulence channel that already exists rather than as a new kernel input, since a burst IS a brief broadband excitation at the constriction. Detection uses the SMOOTHED area, not the phoneme label: what matters is that the tract actually opened, which is the same reason the closure test asserts realised rather than target geometry. `RELEASE_AREA_CM2` is the threshold below which the tract counts as closed, matching the closure the test requires."
      },
      {
        "name": "BURST_AMPLITUDE",
        "kind": "constant",
        "signature": "BURST_AMPLITUDE",
        "summary": ""
      },
      {
        "name": "BURST_TIME_MS",
        "kind": "constant",
        "signature": "BURST_TIME_MS",
        "summary": "Burst decay. ~8 ms puts the transient plus its aspiration in the 10-30 ms range measured for real plosive releases."
      },
      {
        "name": "FRICATION_PLACE_GAIN",
        "kind": "constant",
        "signature": "FRICATION_PLACE_GAIN",
        "summary": ""
      },
      {
        "name": "VOICED_FRICATION_GAIN",
        "kind": "constant",
        "signature": "VOICED_FRICATION_GAIN",
        "summary": ""
      },
      {
        "name": "DEFAULT_ARTICULATION_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_ARTICULATION_CONFIG",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/model/LengthRegulator",
    "path": "agi/reference/particle_voice/model/LengthRegulator.md",
    "source": "agi/particle_voice/model/LengthRegulator.js",
    "import": "/agi/particle_voice/model/LengthRegulator.js",
    "sourceHash": "4f465197f6c3f3eddb6be39697fe9937780b792ac89bdaeb6d868bcdd46292e9",
    "summary": "LengthRegulator.js — Phase 3 ParticleVoice model. Expands per-PHONEME encoder features and prosody into per-FRAME streams, and — because `spec/VoicePlan-v0.md` §5 explicitly assigns it here — handles resampling between the 100 Hz plan frame rate and whatever rate the consumer runs at (the Activity path is 8 ms/frame = 125 Hz, `ActivityContract-v0` §1; the two \"are not required to be equal; any resampling between them is `LengthRegulator.js`'s job, not assumed away here\"). ## F0 is interpolated, features are held These two streams are expanded differently, on purpose: - **F0 is linearly interpolated** between phoneme centres. Holding one value per phoneme would produce a pitch STAIRCASE, and a discontinuous F0 is audible as a click or a warble at every phoneme boundary — the exact artifact the Phase 2 exit gate spent effort eliminating from the waveguide. Interpolating between centres (not edges) means each phoneme's nominal pitch is actually reached at its midpoint. - **Feature vectors are HELD** across a phoneme's frames, not interpolated. They are a learned representation, and blending two phonemes' embeddings produces a vector that means neither. Holding is the standard FastSpeech-style length regulation and is what the downstream `ArticulationHead` expects. - **The voiced flag is held** — it is boolean; interpolating it would invent half-voiced frames that no source model can honour. ## Where it runs CPU-side. The encoder's features are produced on GPU, so using them here costs a readback — but the consumer (`ArticulationHead` → `ParticleTract`) needs per-chunk control values in JS anyway (`areas`, `f0Hz`), so the readback is not avoided by moving this to GPU, only relocated. A GPU `length_regulate` gather kernel is a straightforward follow-up IF profiling later shows the feature upload dominating; the condition that would justify it is stated so the decision is revisitable rather than forgotten.",
    "exports": [
      {
        "name": "LengthRegulator",
        "kind": "class",
        "signature": "class LengthRegulator",
        "summary": ""
      },
      {
        "name": "regulateLength",
        "kind": "function",
        "signature": "regulateLength({ durationFrames, f0Hz, voiced, features = null, featureDim = 0, maxFrames = 1 << 20 })",
        "summary": "Expand per-phoneme streams to per-frame streams. durationFrames: ArrayLike<number>, f0Hz: ArrayLike<number>, voiced: ArrayLike<number>, features?: ArrayLike<number>, featureDim?: number, maxFrames?: number, }} input"
      },
      {
        "name": "resampleFrames",
        "kind": "function",
        "signature": "resampleFrames(regulated, fromRateHz, toRateHz, featureDim = 0)",
        "summary": "Resample per-frame streams from one frame rate to another — the job `spec/VoicePlan-v0.md` §5 assigns to this module for crossing between the 100 Hz plan rate and the 125 Hz Activity rate. F0 is linearly interpolated (it is continuous); voicing and the phoneme index use NEAREST-neighbour, because both are categorical and interpolating them would invent values that do not exist (a half-voiced frame, or a fractional phoneme id)."
      },
      {
        "name": "createLengthRegulator",
        "kind": "function",
        "signature": "createLengthRegulator(options)",
        "summary": ""
      },
      {
        "name": "ACTIVITY_FRAME_RATE_HZ",
        "kind": "constant",
        "signature": "ACTIVITY_FRAME_RATE_HZ",
        "summary": "LengthRegulator.js — Phase 3 ParticleVoice model. Expands per-PHONEME encoder features and prosody into per-FRAME streams, and — because `spec/VoicePlan-v0.md` §5 explicitly assigns it here — handles resampling between the 100 Hz plan frame rate and whatever rate the consumer runs at (the Activity path is 8 ms/frame = 125 Hz, `ActivityContract-v0` §1; the two \"are not required to be equal; any resampling between them is `LengthRegulator.js`'s job, not assumed away here\"). ## F0 is interpolated, features are held These two streams are expanded differently, on purpose: - **F0 is linearly interpolated** between phoneme centres. Holding one value per phoneme would produce a pitch STAIRCASE, and a discontinuous F0 is audible as a click or a warble at every phoneme boundary — the exact artifact the Phase 2 exit gate spent effort eliminating from the waveguide. Interpolating between centres (not edges) means each phoneme's nominal pitch is actually reached at its midpoint. - **Feature vectors are HELD** across a phoneme's frames, not interpolated. They are a learned representation, and blending two phonemes' embeddings produces a vector that means neither. Holding is the standard FastSpeech-style length regulation and is what the downstream `ArticulationHead` expects. - **The voiced flag is held** — it is boolean; interpolating it would invent half-voiced frames that no source model can honour. ## Where it runs CPU-side. The encoder's features are produced on GPU, so using them here costs a readback — but the consumer (`ArticulationHead` → `ParticleTract`) needs per-chunk control values in JS anyway (`areas`, `f0Hz`), so the readback is not avoided by moving this to GPU, only relocated. A GPU `length_regulate` gather kernel is a straightforward follow-up IF profiling later shows the feature upload dominating; the condition that would justify it is stated so the decision is revisitable rather than forgotten."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/model/ParticleVoiceModel",
    "path": "agi/reference/particle_voice/model/ParticleVoiceModel.md",
    "source": "agi/particle_voice/model/ParticleVoiceModel.js",
    "import": "/agi/particle_voice/model/ParticleVoiceModel.js",
    "sourceHash": "ceb7f417da825d08ede6d7eac2c998966dbd1eeb172cec46060c9a7f533a9bce",
    "summary": "ParticleVoiceModel.js — Phase 3 ParticleVoice model. Wires the whole Phase 3 path into one call: **text → PCM**. TextNormalizer → G2P/lexicon → PhonemeSequence → PhonemeEncoder (GPU, optional) → ProsodyPlanner → LengthRegulator → ArticulationHead → NeuralPhysiologyState stream → ParticleTract (GPU acoustic solver) → SafetyLimiter → PCM ## Why there is no separate `SourceVocoder` in Phase 3 The plan lists \"`SourceVocoder.js` basic harmonic + noise + waveguide output\". In Phase 3 that IS `ParticleTract`: it already runs `glottal_lf` (harmonic source), two `constriction_noise` streams (aspiration at the glottis, frication at the constriction) and the Kelly-Lochbaum waveguide, with lip radiation applied internally. Adding a `SourceVocoder` wrapper that only forwarded to it would be a layer with no content. Phase 4's full source-filter Fourier vocoder (bounded spectral residual, MPD/MRD refinement, IFFT + WOLA) is where that module earns its own file — and the seam for it is `renderPhysiology()`, which is already separate from the text frontend. ## Frame rate vs chunk rate: the decimation that matters `ArticulationHead` emits physiology at the 100 Hz plan frame rate (10 ms), but `ParticleTract.renderChunk()` accepts **one control set per chunk** — one area profile, one f0, one constriction index. At 32 kHz with 1024-sample chunks a chunk is 32 ms, so roughly 3 physiology frames fall inside each one. Something has to reduce them, and the choice is audible: - **Areas are AVERAGED** across the frames in a chunk. Picking one frame (say the first) would throw away the smoothing `ArticulationHead` deliberately applied and reintroduce exactly the reflection-coefficient step it exists to prevent. - **F0 is averaged** for the same reason: a chunk-rate pitch staircase is audible as a warble. - **Constriction index is taken by MAJORITY**, not averaged — it is a position index, and the mean of \"junction 20\" and \"junction 6\" is junction 13, a place no phoneme asked for. - **Voicing/amplitudes are averaged**, which naturally cross-fades a voiced→voiceless transition across the chunk rather than switching hard. This decimation is the one place a Phase 3 discontinuity could still enter, so it is concentrated in `_decimateChunk()` and tested directly. ## Breath reservoir `BreathReservoirState`'s one-pole lag is applied CPU-side per chunk to turn `ArticulationHead`'s target subglottal pressure into an actual pressure with inertia, and that pressure scales the glottal excitation. This uses the same `lagRateFromCompliance` formula the GPU `breath_reservoir` kernel expects pre-computed, so the two agree; running it here rather than on GPU avoids a readback for a single scalar per chunk. Documented as a deliberate simplification: a per-SAMPLE reservoir would need the kernel.",
    "exports": [
      {
        "name": "ParticleVoiceModel",
        "kind": "class",
        "signature": "class ParticleVoiceModel",
        "summary": ""
      },
      {
        "name": "DEFAULT_VOICE_MODEL_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_VOICE_MODEL_CONFIG",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/model/PhonemeEncoder",
    "path": "agi/reference/particle_voice/model/PhonemeEncoder.md",
    "source": "agi/particle_voice/model/PhonemeEncoder.js",
    "import": "/agi/particle_voice/model/PhonemeEncoder.js",
    "sourceHash": "4099b66bf7b0331dc1686681d7fde3502c56c671ba3c4b6ef25e49571d91a74e",
    "summary": "PhonemeEncoder.js — Phase 3 ParticleVoice model. Phoneme ids → contextual per-token feature vectors, entirely on GPU: a `PHONEME_COUNT`-row embedding table followed by two Conv1D blocks, each with SiLU, and a final RMSNorm. This is the first Phase 3 component that runs the Phase 1 ParticleNN kernels in composition. Pipeline (7 dispatches, ONE submit per `encode()`): ids [T] → embedding_f16   → [T, D]   sequence-major → transpose_2d    → [D, T]   channel-major  (see \"layout\" below) → conv1d_w8a16    → [H, T] → silu_f16        → [H, T] → conv1d_w8a16    → [O, T] → silu_f16        → [O, T] → transpose_2d    → [T, O]   sequence-major → rmsnorm_f16     → [T, O]   per-token normalization ## Layout: why two transposes are load-bearing The Phase 1 kernels disagree about which axis is major, and each is right for itself: `embedding_f16` gathers rows so it emits **sequence-major** `[numIds, embedDim]`; `conv1d_w8a16` slides a kernel along time so it reads **channel-major** `[inChannels, seqLen]`; `rmsnorm_f16` normalizes rows, so per-token normalization needs sequence-major again. Composing them without the axis swaps would not error — it would silently convolve across the feature axis and produce scrambled activations. `core/transpose_2d.js` was added for exactly this, and the two transposes here are the minimum: SiLU is elementwise and therefore layout-agnostic, so both convolutions run back-to-back in channel-major before a single swap back for the norm. ## Weights are placeholders, and this file says so No Phase 3 training exists yet, so `createDeterministicEncoderWeights()` produces **seeded pseudorandom INT8 weights**. They make the pipeline runnable, shape-correct and reproducible end-to-end; they do NOT make it meaningful. The encoder's output is a deterministic function of its input, which is what Phase 3's downstream components need in order to be built and tested — but nothing here has learned anything about phonetics. Real weights arrive with training; the `weights` argument is the seam. ## Quantization Conv weights are INT8 with a per-output-channel scale, matching `conv1d_w8a16.js`'s packed layout exactly (4 int8 per `u32`, flattened `(inCh, tap)` index, `inChannels * kernelWidth` a multiple of 4 — validated in the constructor rather than left to fail as garbage output).",
    "exports": [
      {
        "name": "PhonemeEncoder",
        "kind": "class",
        "signature": "class PhonemeEncoder",
        "summary": ""
      },
      {
        "name": "packInt8Weights",
        "kind": "function",
        "signature": "packInt8Weights(weights, outChannels, kTotal)",
        "summary": "Pack int8 weights into `u32`s, 4 per word, matching `conv1d_w8a16.js`'s documented `(inCh, tap)` flattening."
      },
      {
        "name": "createDeterministicEncoderWeights",
        "kind": "function",
        "signature": "createDeterministicEncoderWeights(config = {}, seed = 1337)",
        "summary": "Build a complete, reproducible placeholder weight set. See the header: this is scaffolding so the pipeline can be exercised, not a trained model."
      },
      {
        "name": "createPhonemeEncoder",
        "kind": "function",
        "signature": "createPhonemeEncoder(device, options)",
        "summary": ""
      },
      {
        "name": "DEFAULT_ENCODER_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_ENCODER_CONFIG",
        "summary": "Small by design: Phase 3 is a \"tiny deterministic single-speaker model\", not a production encoder."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/model/ProsodyPlanner",
    "path": "agi/reference/particle_voice/model/ProsodyPlanner.md",
    "source": "agi/particle_voice/model/ProsodyPlanner.js",
    "import": "/agi/particle_voice/model/ProsodyPlanner.js",
    "sourceHash": "b8bf23fcb2a94a29ff26b3a723154cddbc038f94fe2335238c0fd17afc2ba9e0",
    "summary": "ProsodyPlanner.js — Phase 3 ParticleVoice model. Predicts per-phoneme **duration**, **F0** and a **voiced flag** from a `PhonemeSequence` (`spec/VoicePlan-v0.md` §4), at the plan frame rate of 100 Hz / 10 ms per frame that spec fixes. ## Rule-based, and the plan says \"minimal\" This is a deterministic rule model, not a trained one: durations come from per-manner base values scaled by stress and boundary position, and F0 from a declination line with stress excursions and a terminal contour selected by punctuation context. Phase 3's goal is \"a tiny deterministic single-speaker model\", and a learned duration/F0 predictor needs aligned speech data this project does not have. The numbers below are therefore **plausible phonetics, not measured values** — they are ordered correctly relative to each other (stops shorter than fricatives, diphthongs longer than monophthongs, stressed longer than unstressed), which is what makes speech intelligible, but they are not fitted to any corpus. `plan()` takes an optional `overrides` bag so a caller can retune without editing this file, and the class boundary is where a learned predictor slots in. ## What it deliberately does not do - **No per-phoneme F0 contour inside a phoneme.** One target per phoneme; smoothing into a continuous per-frame curve is `LengthRegulator.js`'s job, because that is where frame counts exist. Emitting a flat value per phoneme here and interpolating there avoids the audible pitch STEP that a naive per-phoneme hold would produce. - **No accent/emotion conditioning.** `spec/VoicePlan-v0.md` §3's factorized conditioning channels exist, but wiring them is Phase 4+; pretending to condition on them now would be fake.",
    "exports": [
      {
        "name": "ProsodyPlanner",
        "kind": "class",
        "signature": "class ProsodyPlanner",
        "summary": ""
      },
      {
        "name": "createProsodyPlanner",
        "kind": "function",
        "signature": "createProsodyPlanner(config)",
        "summary": ""
      },
      {
        "name": "PLAN_FRAME_RATE_HZ",
        "kind": "constant",
        "signature": "PLAN_FRAME_RATE_HZ",
        "summary": "`spec/VoicePlan-v0.md` §5: the plan frame rate is 100 Hz (10 ms/frame)."
      },
      {
        "name": "PLAN_FRAME_MS",
        "kind": "constant",
        "signature": "PLAN_FRAME_MS",
        "summary": ""
      },
      {
        "name": "BASE_DURATION_MS",
        "kind": "constant",
        "signature": "BASE_DURATION_MS",
        "summary": "Base durations in MILLISECONDS by articulatory manner. Relative ordering is the part that matters (see header); absolute values are plausible defaults."
      },
      {
        "name": "STRUCTURAL_DURATION_MS",
        "kind": "constant",
        "signature": "STRUCTURAL_DURATION_MS",
        "summary": "Structural-token durations, keyed by the punctuation context they sit at (spec §4's Effect column: comma-level is a short pause, terminals are full silences, a paragraph is longer still)."
      },
      {
        "name": "DEFAULT_PROSODY_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_PROSODY_CONFIG",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/autodiff/BackwardGraphCompiler",
    "path": "agi/reference/particle_voice/nn/autodiff/BackwardGraphCompiler.md",
    "source": "agi/particle_voice/nn/autodiff/BackwardGraphCompiler.js",
    "import": "/agi/particle_voice/nn/autodiff/BackwardGraphCompiler.js",
    "sourceHash": "40bed4344aa1afa39a03635f1d31fe032444fde0eb6ab8f48b1392eb864ae7aa",
    "summary": "BackwardGraphCompiler.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11.** Walks a `ParticleTape`'s recorded forward ops in reverse and builds a `GradientGraph` node per op that has a registered `BackwardRuleRegistry` rule (ops without one are skipped, not silently faked with an identity/zero gradient). Refuses to compile anything if the registry is completely empty — the honest current state (no backward rules exist yet for any real kernel) rather than returning a plausible-looking but meaningless empty graph.",
    "exports": [
      {
        "name": "BackwardGraphCompiler",
        "kind": "class",
        "signature": "class BackwardGraphCompiler",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/autodiff/BackwardRuleRegistry",
    "path": "agi/reference/particle_voice/nn/autodiff/BackwardRuleRegistry.md",
    "source": "agi/particle_voice/nn/autodiff/BackwardRuleRegistry.js",
    "import": "/agi/particle_voice/nn/autodiff/BackwardRuleRegistry.js",
    "sourceHash": "7fa8f347e083d9a344b32aba41790913e04fb201aa05fb4396630fe8792f0d2d",
    "summary": "BackwardRuleRegistry.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11.** Maps a forward op name (from `KernelRegistry.js`'s allowlist, e.g. `matmul_w8a16`) to a backward rule: a function that, given the recorded forward op's inputs/outputs/params plus the gradient(s) flowing into its outputs, produces the gradient(s) with respect to its inputs. No rules are registered for any real kernel yet — that is genuinely Phase 11 work (differentiating through INT8 quantization and FP16 rounding correctly is a real design problem, not something to hand-wave here) — this file only fixes the registry shape.",
    "exports": [
      {
        "name": "BackwardRuleRegistry",
        "kind": "class",
        "signature": "class BackwardRuleRegistry",
        "summary": "BackwardRuleRegistry.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11.** Maps a forward op name (from `KernelRegistry.js`'s allowlist, e.g. `matmul_w8a16`) to a backward rule: a function that, given the recorded forward op's inputs/outputs/params plus the gradient(s) flowing into its outputs, produces the gradient(s) with respect to its inputs. No rules are registered for any real kernel yet — that is genuinely Phase 11 work (differentiating through INT8 quantization and FP16 rounding correctly is a real design problem, not something to hand-wave here) — this file only fixes the registry shape."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/autodiff/GradientGraph",
    "path": "agi/reference/particle_voice/nn/autodiff/GradientGraph.md",
    "source": "agi/particle_voice/nn/autodiff/GradientGraph.js",
    "import": "/agi/particle_voice/nn/autodiff/GradientGraph.js",
    "sourceHash": "b2f0dd7bb7f7cf81a7edeedc69afc2719015be50a7c93af47f523ec6cde48882",
    "summary": "GradientGraph.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11.** The backward-pass analogue of `ParticleGraph.js`: nodes are backward-rule applications rather than forward kernels, gradient-tensor names rather than activation-tensor names. Reuses `ParticleGraph`'s DAG/topological-sort machinery directly (a gradient graph's dependency structure is a plain DAG exactly like a forward graph's — no different algorithm is needed) rather than duplicating it, and adds `accumulate()` for the one thing a gradient graph needs that a forward graph never does: multiple edges producing a gradient for the *same* tensor must sum, not overwrite (a tensor used as input to two or more forward ops receives a gradient contribution from each one).",
    "exports": [
      {
        "name": "GradientGraph",
        "kind": "class",
        "signature": "class GradientGraph extends ParticleGraph",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/autodiff/ParticleTape",
    "path": "agi/reference/particle_voice/nn/autodiff/ParticleTape.md",
    "source": "agi/particle_voice/nn/autodiff/ParticleTape.js",
    "import": "/agi/particle_voice/nn/autodiff/ParticleTape.js",
    "sourceHash": "3efdfbe05c920def939bd97d89c68c81aed888637fded444b3929087e226a713",
    "summary": "ParticleTape.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11** (browser training), per the plan's explicit phasing. This establishes the recording-tape contract a future forward pass will write to and `BackwardGraphCompiler.js` will read from — it is NOT exercised by any real training loop yet (R7's training spike used its own hand-wired `TinyAutodiff.js`, deliberately separate from this production module). Records forward-pass operations (op name, input tensor names, output tensor names, and any params the backward rule will need) in execution order, reverse-mode style.",
    "exports": [
      {
        "name": "ParticleTape",
        "kind": "class",
        "signature": "class ParticleTape",
        "summary": "ParticleTape.js — Phase 1 ParticleNN autodiff STUB. **Not wired into anything until Phase 11** (browser training), per the plan's explicit phasing. This establishes the recording-tape contract a future forward pass will write to and `BackwardGraphCompiler.js` will read from — it is NOT exercised by any real training loop yet (R7's training spike used its own hand-wired `TinyAutodiff.js`, deliberately separate from this production module). Records forward-pass operations (op name, input tensor names, output tensor names, and any params the backward rule will need) in execution order, reverse-mode style."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleGraph",
    "path": "agi/reference/particle_voice/nn/core/ParticleGraph.md",
    "source": "agi/particle_voice/nn/core/ParticleGraph.js",
    "import": "/agi/particle_voice/nn/core/ParticleGraph.js",
    "sourceHash": "15daeedd19bebde7375ffc386b807991182723149294f0651afc2f55a664f748",
    "summary": "ParticleGraph.js — Phase 1 ParticleNN core. The uncompiled neural compute graph a model author builds: named nodes with an `op` (must be in `PRNN-v0.md` §3's operator allowlist) and named input/output tensor slots. `PRNNCompiler.js` (Phase 2+) lowers a `ParticleGraph` into a `PRNN-v0` §8 execution plan / `ParticleProgram`; this file only builds and validates the DAG itself (topological sort, cycle/dangling-reference detection) — it does not compile or dispatch anything.",
    "exports": [
      {
        "name": "ParticleGraph",
        "kind": "class",
        "signature": "class ParticleGraph",
        "summary": "ParticleGraph.js — Phase 1 ParticleNN core. The uncompiled neural compute graph a model author builds: named nodes with an `op` (must be in `PRNN-v0.md` §3's operator allowlist) and named input/output tensor slots. `PRNNCompiler.js` (Phase 2+) lowers a `ParticleGraph` into a `PRNN-v0` §8 execution plan / `ParticleProgram`; this file only builds and validates the DAG itself (topological sort, cycle/dangling-reference detection) — it does not compile or dispatch anything."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleKernel",
    "path": "agi/reference/particle_voice/nn/core/ParticleKernel.md",
    "source": "agi/particle_voice/nn/core/ParticleKernel.js",
    "import": "/agi/particle_voice/nn/core/ParticleKernel.js",
    "sourceHash": "123b27d02c1efe426eafe37c3146f0102efc32d503fa815d16b80bb7c72ba380",
    "summary": "ParticleKernel.js — Phase 1 ParticleNN core. Kernel specialization: resolves a `KernelRegistry` entry + concrete params (`useShaderF16`, kernel-specific options like `eps`) into one ready-to-compile unit — WGSL source, entry point, workgroup size, and the explicit bind-group-layout entries `PipelineCache.js`/`KernelValidation.js` proved necessary for `f16` kernels (`DeviceContract-v0.md` §8).",
    "exports": [
      {
        "name": "ParticleKernel",
        "kind": "class",
        "signature": "class ParticleKernel",
        "summary": "ParticleKernel.js — Phase 1 ParticleNN core. Kernel specialization: resolves a `KernelRegistry` entry + concrete params (`useShaderF16`, kernel-specific options like `eps`) into one ready-to-compile unit — WGSL source, entry point, workgroup size, and the explicit bind-group-layout entries `PipelineCache.js`/`KernelValidation.js` proved necessary for `f16` kernels (`DeviceContract-v0.md` §8)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleModel",
    "path": "agi/reference/particle_voice/nn/core/ParticleModel.md",
    "source": "agi/particle_voice/nn/core/ParticleModel.js",
    "import": "/agi/particle_voice/nn/core/ParticleModel.js",
    "sourceHash": "3438a4ebf315d89dc8c95d3a698408c0f95b777d17cfec0682e88738769e8fd2",
    "summary": "ParticleModel.js — Phase 1 ParticleNN core. A loaded model container: a compiled `ParticleProgram` plus its weight memory (`WeightPageStore`/`WeightBindWindow`/`WeightPageUploader`) and `.prnn` header metadata, as one object. Deliberately thin scaffolding at Phase 1 — no real model exists yet (`PRNNReader.js`/`PRNNCompiler.js` that would actually produce one of these from a `.prnn` file are Phase 2+ work); this fixes the shape that loader will hand to callers.",
    "exports": [
      {
        "name": "ParticleModel",
        "kind": "class",
        "signature": "class ParticleModel",
        "summary": "ParticleModel.js — Phase 1 ParticleNN core. A loaded model container: a compiled `ParticleProgram` plus its weight memory (`WeightPageStore`/`WeightBindWindow`/`WeightPageUploader`) and `.prnn` header metadata, as one object. Deliberately thin scaffolding at Phase 1 — no real model exists yet (`PRNNReader.js`/`PRNNCompiler.js` that would actually produce one of these from a `.prnn` file are Phase 2+ work); this fixes the shape that loader will hand to callers."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleProgram",
    "path": "agi/reference/particle_voice/nn/core/ParticleProgram.md",
    "source": "agi/particle_voice/nn/core/ParticleProgram.js",
    "import": "/agi/particle_voice/nn/core/ParticleProgram.js",
    "sourceHash": "1a366f9296f31cdb985fd020356bda7fa5df1bcd347f893666ee22728b91a4ec",
    "summary": "ParticleProgram.js — Phase 1 ParticleNN core. A compiled dispatch sequence: `ParticleGraph`'s topological order lowered into an ordered list of `{ nodeId, kernel: ParticleKernel, inputs, outputs }` steps, one `ParticleKernel` per graph node. `compileAll()` builds every step's GPU pipeline via a shared `PipelineCache` up front (so first dispatch has no compile-stall); actually running the steps each frame is `NeuralExecutor.js`'s job, not this file's.",
    "exports": [
      {
        "name": "ParticleProgram",
        "kind": "class",
        "signature": "class ParticleProgram",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleTensor",
    "path": "agi/reference/particle_voice/nn/core/ParticleTensor.md",
    "source": "agi/particle_voice/nn/core/ParticleTensor.js",
    "import": "/agi/particle_voice/nn/core/ParticleTensor.js",
    "sourceHash": "14e6b39b5d77438d5628526ab3343ff90b8e96eb1d8e891e8c4427beb4586214",
    "summary": "ParticleTensor.js — Phase 1 ParticleNN core. GPU-resident tensor with explicit shape/strides/dtype/quant metadata. Mirrors `agi/tensor/Tensor.js`'s ownership/readback/dispose conventions (reference only, not imported at runtime — the plan requires ParticleNN to stay decoupled from the LLM runtime) but adds the metadata ParticleVoice actually needs: multiple dtypes (`f32`/`f16`/`i8`/`u32`) and optional per-block INT8 quantization scale/zero data alongside the raw buffer.",
    "exports": [
      {
        "name": "ParticleTensor",
        "kind": "class",
        "signature": "class ParticleTensor",
        "summary": "`quant`, when present, describes INT8 block quantization for `dtype: 'i8'` tensors: `{ scheme: 'int8-block', blockSize: u32, scales: Float32Array }` — one scale per `ceil(elementCount / blockSize)` block, dequantized as `value = i8Value * scales[blockIndex]` (zero-point-free, symmetric quantization; PRNN-v0's `dequant_int8` kernel implements this exact scheme — see `nn/kernels/quantization/dequant_int8.js`)."
      },
      {
        "name": "contiguousStrides",
        "kind": "function",
        "signature": "contiguousStrides(shape)",
        "summary": "Row-major contiguous strides for `shape` (element units, not bytes)."
      },
      {
        "name": "tensorElementCount",
        "kind": "function",
        "signature": "tensorElementCount(shape)",
        "summary": ""
      },
      {
        "name": "wrapReadbackBuffer",
        "kind": "function",
        "signature": "wrapReadbackBuffer(dtype, arrayBuffer, size)",
        "summary": "Wrap a raw readback `ArrayBuffer` in the correct typed-array view for `dtype`. Shared by `ParticleTensor` and `ParticleTensorView` so readback interpretation lives in exactly one place."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/core/ParticleTensorView",
    "path": "agi/reference/particle_voice/nn/core/ParticleTensorView.md",
    "source": "agi/particle_voice/nn/core/ParticleTensorView.js",
    "import": "/agi/particle_voice/nn/core/ParticleTensorView.js",
    "sourceHash": "16aa70b107f4a6b95b991ae206e4ff7d211c06dc2e2fb245883abef6bc2e4123",
    "summary": "ParticleTensorView.js — Phase 1 ParticleNN core. A zero-copy view over a parent `ParticleTensor`'s GPU buffer: a different logical shape/strides/byte-offset into the SAME underlying buffer, with no `device.queue` copy and no new buffer allocation. Used for things like binding a single weight page's sub-range, or reading one layer's slice out of a stacked activation tensor, without a `copyBufferToBuffer` pass.",
    "exports": [
      {
        "name": "ParticleTensorView",
        "kind": "class",
        "signature": "class ParticleTensorView",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/KernelRegistry",
    "path": "agi/reference/particle_voice/nn/kernels/KernelRegistry.md",
    "source": "agi/particle_voice/nn/kernels/KernelRegistry.js",
    "import": "/agi/particle_voice/nn/kernels/KernelRegistry.js",
    "sourceHash": "ebc1640bea74a31e76572d90ddf48518bd63f7c87855d72d61f41663d9534d19",
    "summary": "KernelRegistry.js — Phase 1 ParticleNN kernel infrastructure. Maps kernel name -> `shader(params)` generator, mirroring `agi/llm/kernels/KernelRegistry.js`'s design (reference only, not imported — same LLM-runtime-decoupling rule as `PipelineUtils.js`). Each generator here also declares `inputCount` (how many read-only storage bindings it expects before the single read_write output binding) so `createShaderModule`'s caller can build the matching explicit bind-group layout required for `useShaderF16` kernels (see `DeviceContract-v0.md` §8 / `KernelValidation.js`'s `createExplicitLayoutPipeline`) without re-deriving each kernel's binding count by hand.",
    "exports": [
      {
        "name": "KernelRegistry",
        "kind": "class",
        "signature": "class KernelRegistry",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/ShaderComposer",
    "path": "agi/reference/particle_voice/nn/kernels/ShaderComposer.md",
    "source": "agi/particle_voice/nn/kernels/ShaderComposer.js",
    "import": "/agi/particle_voice/nn/kernels/ShaderComposer.js",
    "sourceHash": "18282638eea21ae5e09e66e8c5d20c355144e57389352457ac632a0606ebc938",
    "summary": "ShaderComposer.js — Phase 1 ParticleNN kernel infrastructure. WGSL chunk composition with dependency resolution: register named chunks (each optionally depending on other chunks), then compose an ordered, deduplicated concatenation for a kernel body — so e.g. `int8_pack`'s `unpack_int8` is emitted exactly once even if two requested chunks both depend on it, and always appears before any chunk/body that calls it.",
    "exports": [
      {
        "name": "ShaderComposer",
        "kind": "class",
        "signature": "class ShaderComposer",
        "summary": "ShaderComposer.js — Phase 1 ParticleNN kernel infrastructure. WGSL chunk composition with dependency resolution: register named chunks (each optionally depending on other chunks), then compose an ordered, deduplicated concatenation for a kernel body — so e.g. `int8_pack`'s `unpack_int8` is emitted exactly once even if two requested chunks both depend on it, and always appears before any chunk/body that calls it."
      },
      {
        "name": "createDefaultShaderComposer",
        "kind": "function",
        "signature": "async createDefaultShaderComposer()",
        "summary": "Pre-registers the Phase 1 WGSL chunk set (`math_common`, `int8_pack`, `complex_math`) so a kernel generator can immediately `compose(['math_common', 'int8_pack'], body)` without registering each chunk itself. `f16_utils.js` has no WGSL fragment (it is a host-side-only JS<->f16 bit-conversion utility, per its own module doc) so it is not registered here."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/ShaderDiagnosticFormatter",
    "path": "agi/reference/particle_voice/nn/kernels/ShaderDiagnosticFormatter.md",
    "source": "agi/particle_voice/nn/kernels/ShaderDiagnosticFormatter.js",
    "import": "/agi/particle_voice/nn/kernels/ShaderDiagnosticFormatter.js",
    "sourceHash": "13b19634b694ecc98fc536dbfc72af1111e262398f0de662600f6dfe3b3897d7",
    "summary": "ShaderDiagnosticFormatter.js — Phase 1 ParticleNN kernel infrastructure. Formats `GPUCompilationMessage[]` (from `module.getCompilationInfo()`) into human-readable diagnostics, optionally resolving each message's line number through a `WGSLSourceMap` to name which chunk it came from — a strict superset of `PipelineUtils.js`'s `assertShaderCompiled` error text (which only reports line:col + message, no chunk attribution).",
    "exports": [
      {
        "name": "formatCompilationMessages",
        "kind": "function",
        "signature": "formatCompilationMessages(messages, { sourceMap = null } = {})",
        "summary": ""
      },
      {
        "name": "formatDiagnosticReport",
        "kind": "function",
        "signature": "formatDiagnosticReport(kernelName, messages, options = {})",
        "summary": ""
      },
      {
        "name": "formatErrorsOnly",
        "kind": "function",
        "signature": "formatErrorsOnly(kernelName, messages, options = {})",
        "summary": "Only the `type === 'error'` messages, formatted — the subset that should fail a build/test, distinct from warnings/info the caller may want to log but not fail on."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/WGSLSourceMap",
    "path": "agi/reference/particle_voice/nn/kernels/WGSLSourceMap.md",
    "source": "agi/particle_voice/nn/kernels/WGSLSourceMap.js",
    "import": "/agi/particle_voice/nn/kernels/WGSLSourceMap.js",
    "sourceHash": "e73066c5debd3b3a0c2c309f7adf24988d31a1fffbce8254d3876f6b1e85074b",
    "summary": "WGSLSourceMap.js — Phase 1 ParticleNN kernel infrastructure. Generated-WGSL-line -> originating-chunk mapping. Kernels compose WGSL from named chunks (`ShaderComposer.js`) plus their own body; when the GPU compiler reports an error at line N, this turns that bare line number back into \"which named chunk (or the kernel's own body) that line came from\" — the JS source itself isn't line-mapped (these are template literals, not a separate compilation step), but chunk attribution is usually enough to know where to look.",
    "exports": [
      {
        "name": "WGSLSourceMap",
        "kind": "class",
        "signature": "class WGSLSourceMap",
        "summary": "WGSLSourceMap.js — Phase 1 ParticleNN kernel infrastructure. Generated-WGSL-line -> originating-chunk mapping. Kernels compose WGSL from named chunks (`ShaderComposer.js`) plus their own body; when the GPU compiler reports an error at line N, this turns that bare line number back into \"which named chunk (or the kernel's own body) that line came from\" — the JS source itself isn't line-mapped (these are template literals, not a separate compilation step), but chunk attribution is usually enough to know where to look."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/WgslPreprocessor",
    "path": "agi/reference/particle_voice/nn/kernels/WgslPreprocessor.md",
    "source": "agi/particle_voice/nn/kernels/WgslPreprocessor.js",
    "import": "/agi/particle_voice/nn/kernels/WgslPreprocessor.js",
    "sourceHash": "694bb73ac6f6ee88cdad7dd6c31ebc171c47fa01a199061b9045dcdd18837c3b",
    "summary": "WgslPreprocessor.js — Phase 1 ParticleNN kernel infrastructure. Tagged template `wgsl\\`...\\`` supporting `#if`/`#else`/`#endif` and `#for`/`#endfor` directives, evaluated at kernel-generation time (trusted JS call sites only — this is code generation for the kernel author, not a user-input template engine, so a plain `Function` eval for `#if` expressions is an acceptable, deliberate choice here). Directive syntax (each directive must be alone on its own line): #if <jsExpression>      // evaluated with no closure — pre-interpolate #else                   //   values via `${...}` before the directive #endif                  //   line if the condition depends on a variable #for <name> in <start>..<end>   // half-open range, start/end integers #endfor                          // body may reference `#{name}` (a // literal placeholder distinct from // `${}`, since `${}` is already // resolved by template-literal // substitution before this runs)",
    "exports": [
      {
        "name": "preprocessWgsl",
        "kind": "function",
        "signature": "preprocessWgsl(source)",
        "summary": "Process `#if`/`#else`/`#endif` and `#for`/`#endfor` directives in a flat WGSL source string, line by line."
      },
      {
        "name": "wgsl",
        "kind": "function",
        "signature": "wgsl(strings, ...values)",
        "summary": "Tagged template: joins `strings`/`values` per normal template-literal semantics, then runs `preprocessWgsl` over the result."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/activation/silu_f16",
    "path": "agi/reference/particle_voice/nn/kernels/activation/silu_f16.md",
    "source": "agi/particle_voice/nn/kernels/activation/silu_f16.js",
    "import": "/agi/particle_voice/nn/kernels/activation/silu_f16.js",
    "sourceHash": "deb9b2192f65324386089c61c6330950cf7bbbe36a8bd92b801606b719a9fc2c",
    "summary": "activation/silu_f16.js — Phase 1 ParticleNN kernel. Elementwise SiLU (`x * sigmoid(x)`), composed from `chunks/math_common.js`'s `siluScalar` rather than re-deriving the sigmoid inline.",
    "exports": [
      {
        "name": "siluF16Shader",
        "kind": "function",
        "signature": "siluF16Shader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "SILU_F16_ENTRY_POINT",
        "kind": "constant",
        "signature": "SILU_F16_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "SILU_F16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "SILU_F16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/breath_reservoir",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/breath_reservoir.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/breath_reservoir.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/breath_reservoir.js",
    "sourceHash": "05c936a99ba1cc87307bce066c86376211bc53e62c2814b417ebdaf9f2d7588b",
    "summary": "breath_reservoir.js — Phase 2 ParticleNN articulatory kernel. First-order lag filter: `pressure[n] = pressure[n-1] + (target[n] - pressure[n-1]) * lag_rate`, matching `risk/BreathReservoir.js` (R6A)'s proven recurrence exactly (`lag_rate = 1 - exp(-1/(compliance*sampleRate))` is computed by the caller and passed in, not recomputed here). A first-order IIR recurrence is inherently sequential in time, but each of up to N simultaneous voice/capsule *instances* is fully independent — `workgroup_id.x` selects the instance, and a single thread per workgroup walks that instance's whole chunk sequentially (cheap: a chunk is a few hundred samples, and GPU dispatch overhead dominates at that size anyway, so there is nothing to gain from parallelizing within one instance's strictly-sequential recurrence).",
    "exports": [
      {
        "name": "breathReservoirShader",
        "kind": "function",
        "signature": "breathReservoirShader()",
        "summary": ""
      },
      {
        "name": "BREATH_RESERVOIR_ENTRY_POINT",
        "kind": "constant",
        "signature": "BREATH_RESERVOIR_ENTRY_POINT",
        "summary": "breath_reservoir.js — Phase 2 ParticleNN articulatory kernel. First-order lag filter: `pressure[n] = pressure[n-1] + (target[n] - pressure[n-1]) * lag_rate`, matching `risk/BreathReservoir.js` (R6A)'s proven recurrence exactly (`lag_rate = 1 - exp(-1/(compliance*sampleRate))` is computed by the caller and passed in, not recomputed here). A first-order IIR recurrence is inherently sequential in time, but each of up to N simultaneous voice/capsule *instances* is fully independent — `workgroup_id.x` selects the instance, and a single thread per workgroup walks that instance's whole chunk sequentially (cheap: a chunk is a few hundred samples, and GPU dispatch overhead dominates at that size anyway, so there is nothing to gain from parallelizing within one instance's strictly-sequential recurrence)."
      },
      {
        "name": "BREATH_RESERVOIR_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "BREATH_RESERVOIR_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/constriction_noise",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/constriction_noise.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/constriction_noise.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/constriction_noise.js",
    "sourceHash": "ef910ca35007fa674e4288bc74ad062b4fb9b6d3606345153ed74d567a4a72e7",
    "summary": "constriction_noise.js — Phase 2 ParticleNN articulatory kernel. Turbulence/frication noise generator for a chosen internal tract junction (fricatives, plosive bursts) — deliberately a SEPARATE kernel from `speech/noise_source.js` rather than reusing it directly, because the two have genuinely different shapes: `noise_source.js` generates one flat, constant-amplitude signal for the whole dispatch (aspiration noise mixed at the glottis, `risk/GlottalSource.js` (R6)'s `aspirationLevel`), while frication noise needs a per-sample, per-instance TIME-VARYING amplitude envelope (driven by how tightly a constriction is formed, which changes continuously as an articulator moves) and per-instance start index/seed so N simultaneous voices/characters never share correlated turbulence. Sharing the same underlying hash rather than inventing a second one is still correct reuse: `noise_at()` below is the identical two-round Murmur-style avalanche hash as `noise_source.js`/ `risk/GlottalSource.js`'s `noiseAt` (same technique as `StreamingContinuitySource.js`), just applied per-instance with an amplitude that varies per output sample instead of being a single dispatch-wide uniform scalar. `tract_waveguide.js` does not (yet) consume this kernel's output at a `constrictionIndex` internally (its own docstring flags that as deferred, matching R6's `step()`'s own `constrictionIndex`/ `constrictionNoiseSample` option) — this kernel only produces the turbulence signal; injecting it into a specific internal tract junction is a `ParticleTract` orchestration concern for a later phase, once the model that decides the constriction's time-varying amplitude envelope exists. Three spectral shapes are supported: - `direct` `noise_at()` (flat/white-ish), the default reference; - `lowpass` block-interpolated `noise_at()` for glottal aspiration, giving the noise a predominantly low-frequency, breathy character rather than a hissy fricative one; - `derivative` first-differenced `noise_at()` (blue, +6 dB/octave) for oral frication and plosive bursts, where the high-frequency emphasis makes consonants like /s/ and /t/ pop without changing the vowel source.",
    "exports": [
      {
        "name": "constrictionNoiseShader",
        "kind": "function",
        "signature": "constrictionNoiseShader({ spectralShape = 'direct', lowpassHold = 32 } = {})",
        "summary": ""
      },
      {
        "name": "CONSTRICTION_NOISE_ENTRY_POINT",
        "kind": "constant",
        "signature": "CONSTRICTION_NOISE_ENTRY_POINT",
        "summary": "constriction_noise.js — Phase 2 ParticleNN articulatory kernel. Turbulence/frication noise generator for a chosen internal tract junction (fricatives, plosive bursts) — deliberately a SEPARATE kernel from `speech/noise_source.js` rather than reusing it directly, because the two have genuinely different shapes: `noise_source.js` generates one flat, constant-amplitude signal for the whole dispatch (aspiration noise mixed at the glottis, `risk/GlottalSource.js` (R6)'s `aspirationLevel`), while frication noise needs a per-sample, per-instance TIME-VARYING amplitude envelope (driven by how tightly a constriction is formed, which changes continuously as an articulator moves) and per-instance start index/seed so N simultaneous voices/characters never share correlated turbulence. Sharing the same underlying hash rather than inventing a second one is still correct reuse: `noise_at()` below is the identical two-round Murmur-style avalanche hash as `noise_source.js`/ `risk/GlottalSource.js`'s `noiseAt` (same technique as `StreamingContinuitySource.js`), just applied per-instance with an amplitude that varies per output sample instead of being a single dispatch-wide uniform scalar. `tract_waveguide.js` does not (yet) consume this kernel's output at a `constrictionIndex` internally (its own docstring flags that as deferred, matching R6's `step()`'s own `constrictionIndex`/ `constrictionNoiseSample` option) — this kernel only produces the turbulence signal; injecting it into a specific internal tract junction is a `ParticleTract` orchestration concern for a later phase, once the model that decides the constriction's time-varying amplitude envelope exists. Three spectral shapes are supported: - `direct` `noise_at()` (flat/white-ish), the default reference; - `lowpass` block-interpolated `noise_at()` for glottal aspiration, giving the noise a predominantly low-frequency, breathy character rather than a hissy fricative one; - `derivative` first-differenced `noise_at()` (blue, +6 dB/octave) for oral frication and plosive bursts, where the high-frequency emphasis makes consonants like /s/ and /t/ pop without changing the vowel source."
      },
      {
        "name": "CONSTRICTION_NOISE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "CONSTRICTION_NOISE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/glottal_lf",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/glottal_lf.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/glottal_lf.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/glottal_lf.js",
    "sourceHash": "1310ff7266290c426809da09eb2155d5b5b5f8cad49461cf445230069c1aca8c",
    "summary": "glottal_lf.js — Phase 2 ParticleNN articulatory kernel. The full Liljencrants-Fant (LF) glottal flow DERIVATIVE model, replacing `speech/harmonic_source.js`'s raised-cosine \"LF-lite\" stand-in (`risk/GlottalSource.js` (R6)'s own docstring calls that shape a deliberate simplification and names `articulatory/glottal_lf.js` as the real implementation it stands in for). Per period, given control instants Te (glottal closure / main excitation, 0 < Te < T0), Tp (peak-flow instant, 0 < Tp < Te), Ta (return-phase time constant), and Ee (excitation strength), the classic two-segment LF waveform is: E(t) = E0 * exp(alpha*t) * sin(wg*t)                            0<=t<Te E(t) = -(Ee/(eps*Ta)) * (exp(-eps*(t-Te)) - exp(-eps*(T0-Te)))  Te<=t<T0 with `wg = pi/Tp`. `E0`, `alpha`, and `eps` are NOT free parameters — they are pinned by three physical constraints this file's author derived and verified algebraically (rather than recalling a textbook formula from memory, to avoid presenting an unverified transcendental equation as fact): 1. Boundary match at Te: E(Te-) = -Ee, giving `E0 = -Ee / (exp(alpha*Te) * sin(wg*Te))`. 2. `eps` solves `eps*Ta = 1 - exp(-eps*(T0-Te))` (the standard LF return-phase time-constant equation) via Newton-Raphson with the exact analytic derivative `f(eps)=eps*Ta-1+exp(-eps*(T0-Te))`, `f'(eps)=Ta-(T0-Te)*exp(-eps*(T0-Te))`, started at `eps0=1/Ta`. 3. `alpha` solves the area-balance constraint (glottal flow is periodic, so its derivative integrates to zero over one full period): substituting constraint 1 into the closed-form `int[0,Te] E0*exp(alpha*t)*sin(wg*t) dt` (standard `exp(at)sin(bt)` antiderivative) and the closed-form return-phase integral (simplified using constraint 2) gives `Area(alpha) = -Ee/sin(wg*Te) * N(alpha)/D(alpha)` where `N(alpha) = alpha*sin(wg*Te) - wg*cos(wg*Te) + wg*exp(-alpha*Te)` and `D(alpha) = alpha^2 + wg^2`; this is solved for `Area(alpha) = Ee/eps - Ee*(T0-Te)*exp(-eps*(T0-Te))/(eps*Ta)` via Newton-Raphson using the exact analytic derivative of `N/D` (quotient rule from `N'(alpha) = sin(wg*Te) - wg*Te*exp(-alpha*Te)`, `D'(alpha) = 2*alpha`), started at `alpha0 = 0` (a safe, singularity- free starting point: `D(0) = wg^2 != 0`). Both Newton solves run for a fixed, generous iteration count (8 each) — cheap scalar math, and every thread in this kernel redoes the same per-instance solve redundantly rather than solving once and broadcasting through workgroup memory: unlike `tract_waveguide.js`'s per-sample recurrence, LF generation has NO cross-sample dependency at all (like `harmonic_source.js`/`noise_source.js`), so the simplest correct design is one thread per (instance, sample) output element, matching those kernels' flat-parallel convention exactly. If per-period solve cost ever matters at scale, hoisting it into its own once-per-instance kernel (mirroring `tract_transfer.js`'s role for `tract_waveguide.js`) is a straightforward follow-up, not a design change. Every control instant is expressed in SAMPLES (not seconds), matching `harmonic_source.js`'s `period_samples`/`open_samples`/`start_phase` convention and this kernel's own absolute-sample-counter continuity requirement across chunk boundaries.",
    "exports": [
      {
        "name": "solveLfShapeParameters",
        "kind": "function",
        "signature": "solveLfShapeParameters({ t0, te, tp, ta, ee })",
        "summary": "CPU-side reference implementation of the exact same closed-form/Newton- Raphson math the WGSL kernel below runs — used by the parity test and documented here as the canonical derivation any future GPU-side per-instance solve kernel (see docstring) must also match bit-for-bit in structure (same iteration count, same starting values)."
      },
      {
        "name": "evaluateLfSample",
        "kind": "function",
        "signature": "evaluateLfSample(cyclePos, { t0, te, ta, ee, wg, eps, alpha, e0 })",
        "summary": "CPU-side reference for a single sample's LF value, given already-solved shape parameters — matches the WGSL kernel's per-sample branch exactly."
      },
      {
        "name": "glottalLfShader",
        "kind": "function",
        "signature": "glottalLfShader()",
        "summary": ""
      },
      {
        "name": "GLOTTAL_LF_ENTRY_POINT",
        "kind": "constant",
        "signature": "GLOTTAL_LF_ENTRY_POINT",
        "summary": "glottal_lf.js — Phase 2 ParticleNN articulatory kernel. The full Liljencrants-Fant (LF) glottal flow DERIVATIVE model, replacing `speech/harmonic_source.js`'s raised-cosine \"LF-lite\" stand-in (`risk/GlottalSource.js` (R6)'s own docstring calls that shape a deliberate simplification and names `articulatory/glottal_lf.js` as the real implementation it stands in for). Per period, given control instants Te (glottal closure / main excitation, 0 < Te < T0), Tp (peak-flow instant, 0 < Tp < Te), Ta (return-phase time constant), and Ee (excitation strength), the classic two-segment LF waveform is: E(t) = E0 * exp(alpha*t) * sin(wg*t)                            0<=t<Te E(t) = -(Ee/(eps*Ta)) * (exp(-eps*(t-Te)) - exp(-eps*(T0-Te)))  Te<=t<T0 with `wg = pi/Tp`. `E0`, `alpha`, and `eps` are NOT free parameters — they are pinned by three physical constraints this file's author derived and verified algebraically (rather than recalling a textbook formula from memory, to avoid presenting an unverified transcendental equation as fact): 1. Boundary match at Te: E(Te-) = -Ee, giving `E0 = -Ee / (exp(alpha*Te) * sin(wg*Te))`. 2. `eps` solves `eps*Ta = 1 - exp(-eps*(T0-Te))` (the standard LF return-phase time-constant equation) via Newton-Raphson with the exact analytic derivative `f(eps)=eps*Ta-1+exp(-eps*(T0-Te))`, `f'(eps)=Ta-(T0-Te)*exp(-eps*(T0-Te))`, started at `eps0=1/Ta`. 3. `alpha` solves the area-balance constraint (glottal flow is periodic, so its derivative integrates to zero over one full period): substituting constraint 1 into the closed-form `int[0,Te] E0*exp(alpha*t)*sin(wg*t) dt` (standard `exp(at)sin(bt)` antiderivative) and the closed-form return-phase integral (simplified using constraint 2) gives `Area(alpha) = -Ee/sin(wg*Te) * N(alpha)/D(alpha)` where `N(alpha) = alpha*sin(wg*Te) - wg*cos(wg*Te) + wg*exp(-alpha*Te)` and `D(alpha) = alpha^2 + wg^2`; this is solved for `Area(alpha) = Ee/eps - Ee*(T0-Te)*exp(-eps*(T0-Te))/(eps*Ta)` via Newton-Raphson using the exact analytic derivative of `N/D` (quotient rule from `N'(alpha) = sin(wg*Te) - wg*Te*exp(-alpha*Te)`, `D'(alpha) = 2*alpha`), started at `alpha0 = 0` (a safe, singularity- free starting point: `D(0) = wg^2 != 0`). Both Newton solves run for a fixed, generous iteration count (8 each) — cheap scalar math, and every thread in this kernel redoes the same per-instance solve redundantly rather than solving once and broadcasting through workgroup memory: unlike `tract_waveguide.js`'s per-sample recurrence, LF generation has NO cross-sample dependency at all (like `harmonic_source.js`/`noise_source.js`), so the simplest correct design is one thread per (instance, sample) output element, matching those kernels' flat-parallel convention exactly. If per-period solve cost ever matters at scale, hoisting it into its own once-per-instance kernel (mirroring `tract_transfer.js`'s role for `tract_waveguide.js`) is a straightforward follow-up, not a design change. Every control instant is expressed in SAMPLES (not seconds), matching `harmonic_source.js`'s `period_samples`/`open_samples`/`start_phase` convention and this kernel's own absolute-sample-counter continuity requirement across chunk boundaries."
      },
      {
        "name": "GLOTTAL_LF_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "GLOTTAL_LF_WORKGROUP_SIZE",
        "summary": ""
      },
      {
        "name": "GLOTTAL_LF_NEWTON_ITERATIONS",
        "kind": "constant",
        "signature": "GLOTTAL_LF_NEWTON_ITERATIONS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/lip_radiation",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/lip_radiation.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/lip_radiation.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/lip_radiation.js",
    "sourceHash": "10e584e9e268e4f325ffe169025591a1516586c2a40788083d0eddd2b222be06",
    "summary": "lip_radiation.js — Phase 2 ParticleNN articulatory kernel. Idealized lip-radiation filter: a first difference, `radiated[n] = raw[n] - raw[n-1]`, matching `risk/KellyLochbaumWaveguide.js` (R6)'s `step()` exactly (`radiated = outputRaw - prevOutputRaw`) — the differentiating characteristic of radiation from an open end. Despite being defined via a one-sample recurrence, this is embarrassingly parallel: `raw[n-1]` for `n > 0` is just the previous element of the *same* input buffer (already fully computed, no accumulation), and only `n = 0` needs externally-supplied state (`prev_last_sample`, the previous chunk's final raw sample) — so every output sample can be computed independently with no sequential loop at all.",
    "exports": [
      {
        "name": "lipRadiationShader",
        "kind": "function",
        "signature": "lipRadiationShader()",
        "summary": ""
      },
      {
        "name": "LIP_RADIATION_ENTRY_POINT",
        "kind": "constant",
        "signature": "LIP_RADIATION_ENTRY_POINT",
        "summary": "lip_radiation.js — Phase 2 ParticleNN articulatory kernel. Idealized lip-radiation filter: a first difference, `radiated[n] = raw[n] - raw[n-1]`, matching `risk/KellyLochbaumWaveguide.js` (R6)'s `step()` exactly (`radiated = outputRaw - prevOutputRaw`) — the differentiating characteristic of radiation from an open end. Despite being defined via a one-sample recurrence, this is embarrassingly parallel: `raw[n-1]` for `n > 0` is just the previous element of the *same* input buffer (already fully computed, no accumulation), and only `n = 0` needs externally-supplied state (`prev_last_sample`, the previous chunk's final raw sample) — so every output sample can be computed independently with no sequential loop at all."
      },
      {
        "name": "LIP_RADIATION_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "LIP_RADIATION_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/nasal_junction",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/nasal_junction.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/nasal_junction.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/nasal_junction.js",
    "sourceHash": "734baaf1aa299d5c3bd78778865ce973467b8a232da59f37376895aff24d4d59",
    "summary": "nasal_junction.js — Phase 2 ParticleNN articulatory kernel. Production nasal branch: a second, independent Kelly-Lochbaum tube chain generalizing risk/KellyLochbaumWaveguide.js (R6)'s proven createNasalBranch().step() to run a whole sample chunk times N instances on GPU, using the exact same one-thread-per-section / double-buffered-workgroup-memory / sequential-workgroupBarrier-per-sample structure as tract_waveguide.js (see that file's docstring for the full parallelization rationale, not repeated here). Differences from tract_waveguide.js's oral tract, matching R6's createNasalBranch() exactly: - The near end has no reflection term at all: nextRight[0] = inputFlow (R6's oral tract instead does sourceFlow + glottalReflection * left[0]) — the nasal branch is driven directly by a coupled fraction of an oral-tract tap signal, not its own glottal source. - The far end uses a single nostrilReflection coefficient (R6's lipReflection equivalent). `radiation_difference` selects whether the first-difference radiation filter is applied: 1.0 for R6's flow-domain parity path, 0.0 for ParticleTract's LF-derivative-domain production path. This kernel does not itself compute the oral-tract tap signal fed into inputFlow — that is tract_waveguide.js's nasal_tap output block, and inputFlow is an ordinary per-instance per-sample input buffer here, matching every other Phase 2 kernel's convention of not silently reaching into another kernel's state. input_gain is the velum/nasal COUPLING fraction. R6's createNasalBranch() has no such parameter because its caller pre-multiplied the tap signal (nasalCoupling * nasalTapSignal) before calling step(). On GPU that would force either a whole extra scaling kernel or a CPU round-trip in the middle of an otherwise GPU-resident chunk, so the scalar lives here instead — exactly where this file's own docstring already says the branch is \"driven by a coupled fraction of an oral-tract tap signal\". input_gain = 1.0 reproduces R6's step() bit-for-bit, which is what the parity test in phase2-dsp.html pins.",
    "exports": [
      {
        "name": "nasalJunctionOutputLayout",
        "kind": "function",
        "signature": "nasalJunctionOutputLayout(numSections, numSamples, numInstances)",
        "summary": "Shared flat-output-buffer layout — mirrors tractWaveguideOutputLayout() exactly (same three persisted-state pieces plus the radiated signal), kept as its own function per kernel so a future layout change to one tract type can never silently affect the other."
      },
      {
        "name": "nasalJunctionShader",
        "kind": "function",
        "signature": "nasalJunctionShader()",
        "summary": ""
      },
      {
        "name": "NASAL_JUNCTION_MAX_SECTIONS",
        "kind": "constant",
        "signature": "NASAL_JUNCTION_MAX_SECTIONS",
        "summary": "nasal_junction.js — Phase 2 ParticleNN articulatory kernel. Production nasal branch: a second, independent Kelly-Lochbaum tube chain generalizing risk/KellyLochbaumWaveguide.js (R6)'s proven createNasalBranch().step() to run a whole sample chunk times N instances on GPU, using the exact same one-thread-per-section / double-buffered-workgroup-memory / sequential-workgroupBarrier-per-sample structure as tract_waveguide.js (see that file's docstring for the full parallelization rationale, not repeated here). Differences from tract_waveguide.js's oral tract, matching R6's createNasalBranch() exactly: - The near end has no reflection term at all: nextRight[0] = inputFlow (R6's oral tract instead does sourceFlow + glottalReflection * left[0]) — the nasal branch is driven directly by a coupled fraction of an oral-tract tap signal, not its own glottal source. - The far end uses a single nostrilReflection coefficient (R6's lipReflection equivalent). `radiation_difference` selects whether the first-difference radiation filter is applied: 1.0 for R6's flow-domain parity path, 0.0 for ParticleTract's LF-derivative-domain production path. This kernel does not itself compute the oral-tract tap signal fed into inputFlow — that is tract_waveguide.js's nasal_tap output block, and inputFlow is an ordinary per-instance per-sample input buffer here, matching every other Phase 2 kernel's convention of not silently reaching into another kernel's state. input_gain is the velum/nasal COUPLING fraction. R6's createNasalBranch() has no such parameter because its caller pre-multiplied the tap signal (nasalCoupling * nasalTapSignal) before calling step(). On GPU that would force either a whole extra scaling kernel or a CPU round-trip in the middle of an otherwise GPU-resident chunk, so the scalar lives here instead — exactly where this file's own docstring already says the branch is \"driven by a coupled fraction of an oral-tract tap signal\". input_gain = 1.0 reproduces R6's step() bit-for-bit, which is what the parity test in phase2-dsp.html pins."
      },
      {
        "name": "NASAL_JUNCTION_ENTRY_POINT",
        "kind": "constant",
        "signature": "NASAL_JUNCTION_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "NASAL_JUNCTION_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "NASAL_JUNCTION_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/physiology_feedback_reduce",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/physiology_feedback_reduce.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/physiology_feedback_reduce.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/physiology_feedback_reduce.js",
    "sourceHash": "19003236581791c2a69dadf65ea6522e6b74738663abdc10aec1fc76f5f0b08c",
    "summary": "physiology_feedback_reduce.js — Phase 2 ParticleNN articulatory kernel. Reduces a high-rate (32 kHz per `ARCHITECTURE.md` §3) per-sample waveguide/tract signal (e.g. `tract_waveguide.js`'s radiated output, or a raw right/left traveling-wave channel) down to one (mean, RMS) pair per fixed-size frame — the `PhysiologyFeedbackState` `ARCHITECTURE.md` §4 names as \"a reduced/decimated feedback signal from the acoustic solve back toward the model\", so the model's control-rate loop (100 Hz per `ARCHITECTURE.md` §3) can see a compact summary of what the acoustic solver actually produced last frame without reading back and processing the full high-rate PCM itself. `samples_per_frame` is expected to be one of the plan's fixed legal chunk buckets (first 12/16/24 frames, steady 32/48/64 frames at the activity frame size, or a caller-chosen block size for this specific feedback path) — this kernel does not itself enforce bucket membership, it just requires `num_samples == num_frames * samples_per_frame` exactly (no partial trailing frame), matching every other Phase 2 kernel's expectation that framing/bucketing is decided by the caller (`stft.js`/`overlap_add.js` take the same stance). One thread per (instance, frame) pair does its own small sequential reduction over `samples_per_frame` samples — deliberately not a parallel-reduction tree, since typical frame sizes here (tens to a few hundred samples) are small enough that per-thread sequential summation is simpler and cheaper than the synchronization a tree reduction would need, the same reasoning `breath_reservoir.js`'s per-instance single-thread recurrence gives for its own necessarily-sequential loop (this kernel's reduction isn't sequential for correctness, just small enough that a loop is the pragmatic choice).",
    "exports": [
      {
        "name": "physiologyFeedbackReduceOutputLayout",
        "kind": "function",
        "signature": "physiologyFeedbackReduceOutputLayout(numFrames, numInstances)",
        "summary": "Packed-output layout: [mean block (num_instances*num_frames)] then [rms block (num_instances*num_frames)] — kept as its own function so the WGSL offsets and any JS-side reader stay in lockstep, matching `tractWaveguideOutputLayout()`'s precedent."
      },
      {
        "name": "physiologyFeedbackReduceShader",
        "kind": "function",
        "signature": "physiologyFeedbackReduceShader()",
        "summary": ""
      },
      {
        "name": "PHYSIOLOGY_FEEDBACK_REDUCE_ENTRY_POINT",
        "kind": "constant",
        "signature": "PHYSIOLOGY_FEEDBACK_REDUCE_ENTRY_POINT",
        "summary": "physiology_feedback_reduce.js — Phase 2 ParticleNN articulatory kernel. Reduces a high-rate (32 kHz per `ARCHITECTURE.md` §3) per-sample waveguide/tract signal (e.g. `tract_waveguide.js`'s radiated output, or a raw right/left traveling-wave channel) down to one (mean, RMS) pair per fixed-size frame — the `PhysiologyFeedbackState` `ARCHITECTURE.md` §4 names as \"a reduced/decimated feedback signal from the acoustic solve back toward the model\", so the model's control-rate loop (100 Hz per `ARCHITECTURE.md` §3) can see a compact summary of what the acoustic solver actually produced last frame without reading back and processing the full high-rate PCM itself. `samples_per_frame` is expected to be one of the plan's fixed legal chunk buckets (first 12/16/24 frames, steady 32/48/64 frames at the activity frame size, or a caller-chosen block size for this specific feedback path) — this kernel does not itself enforce bucket membership, it just requires `num_samples == num_frames * samples_per_frame` exactly (no partial trailing frame), matching every other Phase 2 kernel's expectation that framing/bucketing is decided by the caller (`stft.js`/`overlap_add.js` take the same stance). One thread per (instance, frame) pair does its own small sequential reduction over `samples_per_frame` samples — deliberately not a parallel-reduction tree, since typical frame sizes here (tens to a few hundred samples) are small enough that per-thread sequential summation is simpler and cheaper than the synchronization a tree reduction would need, the same reasoning `breath_reservoir.js`'s per-instance single-thread recurrence gives for its own necessarily-sequential loop (this kernel's reduction isn't sequential for correctness, just small enough that a loop is the pragmatic choice)."
      },
      {
        "name": "PHYSIOLOGY_FEEDBACK_REDUCE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "PHYSIOLOGY_FEEDBACK_REDUCE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/signal_add",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/signal_add.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/signal_add.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/signal_add.js",
    "sourceHash": "63ef0eb74c68d996aad31679a2b66ace574c90f5c541d582437772da18c423b3",
    "summary": "signal_add.js — Phase 2 ParticleNN articulatory kernel. Elementwise sum of two equal-shaped signals: `out[i] = a[i] + b[i]`. `ParticleTract.js` needs exactly this twice, at the two points its signal graph merges: 1. **Glottal source mix** — `voicing` (`glottal_lf.js`) + `aspiration` (`constriction_noise.js` with a slowly-varying envelope) into the `source_flow` that `tract_waveguide.js` injects at the glottis. 2. **Output mix** — the oral tract's `radiated` signal plus the nasal branch's `radiated` signal (`nasal_junction.js`, already scaled by its own `input_gain` coupling), producing the final PCM. Note that FRICATION is deliberately NOT a third term here. Turbulence belongs at the constriction, not the glottis, so it is fed to `tract_waveguide.js`'s `constriction_noise` input and injected at `constriction_index` — only the cavity in front of the constriction then filters it, which is what makes a fricative sound like a fricative rather than a whisper. That is why this kernel takes two inputs and not three. This kernel exists because nothing in the registry could do it. There was no elementwise-add op, and `speech/crossfade.js` — the only existing two-signal combiner — is NOT a mixer: it sweeps its ratio across the buffer (`t = i/(length-1)`), making it a one-shot transition. Using it here would ramp the voicing/aspiration balance from 0 to 1 across every chunk. There are no gain inputs, deliberately: every amplitude this could scale is already a first-class parameter of the kernel that PRODUCES the term — `glottal_lf.js`'s per-instance `ee`, `constriction_noise.js`'s per-sample `amplitude_envelope`, and `nasal_junction.js`'s `input_gain`. Duplicating that control here would invite the two copies to disagree. Flat-parallel with no cross-sample dependency (one thread per output element), matching `lip_radiation.js`/`constriction_noise.js`. Instance layout is implicit: the caller passes `total_elements = num_instances * num_samples` and both inputs share the same `[num_instances, num_samples]` row-major shape, so no per-instance indexing is needed at all.",
    "exports": [
      {
        "name": "signalAddShader",
        "kind": "function",
        "signature": "signalAddShader()",
        "summary": ""
      },
      {
        "name": "SIGNAL_ADD_ENTRY_POINT",
        "kind": "constant",
        "signature": "SIGNAL_ADD_ENTRY_POINT",
        "summary": "signal_add.js — Phase 2 ParticleNN articulatory kernel. Elementwise sum of two equal-shaped signals: `out[i] = a[i] + b[i]`. `ParticleTract.js` needs exactly this twice, at the two points its signal graph merges: 1. **Glottal source mix** — `voicing` (`glottal_lf.js`) + `aspiration` (`constriction_noise.js` with a slowly-varying envelope) into the `source_flow` that `tract_waveguide.js` injects at the glottis. 2. **Output mix** — the oral tract's `radiated` signal plus the nasal branch's `radiated` signal (`nasal_junction.js`, already scaled by its own `input_gain` coupling), producing the final PCM. Note that FRICATION is deliberately NOT a third term here. Turbulence belongs at the constriction, not the glottis, so it is fed to `tract_waveguide.js`'s `constriction_noise` input and injected at `constriction_index` — only the cavity in front of the constriction then filters it, which is what makes a fricative sound like a fricative rather than a whisper. That is why this kernel takes two inputs and not three. This kernel exists because nothing in the registry could do it. There was no elementwise-add op, and `speech/crossfade.js` — the only existing two-signal combiner — is NOT a mixer: it sweeps its ratio across the buffer (`t = i/(length-1)`), making it a one-shot transition. Using it here would ramp the voicing/aspiration balance from 0 to 1 across every chunk. There are no gain inputs, deliberately: every amplitude this could scale is already a first-class parameter of the kernel that PRODUCES the term — `glottal_lf.js`'s per-instance `ee`, `constriction_noise.js`'s per-sample `amplitude_envelope`, and `nasal_junction.js`'s `input_gain`. Duplicating that control here would invite the two copies to disagree. Flat-parallel with no cross-sample dependency (one thread per output element), matching `lip_radiation.js`/`constriction_noise.js`. Instance layout is implicit: the caller passes `total_elements = num_instances * num_samples` and both inputs share the same `[num_instances, num_samples]` row-major shape, so no per-instance indexing is needed at all."
      },
      {
        "name": "SIGNAL_ADD_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "SIGNAL_ADD_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/tract_transfer",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/tract_transfer.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/tract_transfer.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/tract_transfer.js",
    "sourceHash": "847c47587a8d0f908359c38795230967cd94abde570e5b705bb35eab1adda1c7",
    "summary": "tract_transfer.js — Phase 2 ParticleNN articulatory kernel. Converts a per-instance tube cross-sectional area profile into Kelly- Lochbaum junction reflection coefficients — the exact formula `risk/KellyLochbaumWaveguide.js` (R6)'s proven `reflectionCoefficients()` uses: `k[i] = (A[i] - A[i+1]) / (A[i] + A[i+1] + 1e-6)`. `tract_waveguide.js`'s own docstring notes junction coefficients are \"computed CPU-side by the caller... not recomputed here\", matching `mel_filterbank.js`'s precedent for a fixed-shape precomputation — that is still correct for a STATIC area profile. This kernel exists for the case the plan's `NeuralPhysiologyState.TractGestureHead` implies: an area profile predicted fresh every control frame (100 Hz per `ARCHITECTURE.md` §3), which would otherwise force a GPU-to-CPU-to-GPU round trip every frame just to run one elementwise division — exactly the per-frame readback stall the plan's Production Contract and `engine/core/gpu/VGPUReadbackQueue.js`-style staged-readback convention are meant to avoid. Keeping the whole `TractGestureHead -> junction_k -> tract_waveguide.js` path on GPU is what \"single owned execution path\" requires once area profiles become per-frame instead of per-utterance. Purely elementwise across `(instance, junction)` pairs — no inter-junction dependency at all (unlike `tract_waveguide.js`'s per-sample recurrence), so this is one thread per output element with no workgroup memory or barriers needed.",
    "exports": [
      {
        "name": "tractTransferShader",
        "kind": "function",
        "signature": "tractTransferShader()",
        "summary": ""
      },
      {
        "name": "TRACT_TRANSFER_ENTRY_POINT",
        "kind": "constant",
        "signature": "TRACT_TRANSFER_ENTRY_POINT",
        "summary": "tract_transfer.js — Phase 2 ParticleNN articulatory kernel. Converts a per-instance tube cross-sectional area profile into Kelly- Lochbaum junction reflection coefficients — the exact formula `risk/KellyLochbaumWaveguide.js` (R6)'s proven `reflectionCoefficients()` uses: `k[i] = (A[i] - A[i+1]) / (A[i] + A[i+1] + 1e-6)`. `tract_waveguide.js`'s own docstring notes junction coefficients are \"computed CPU-side by the caller... not recomputed here\", matching `mel_filterbank.js`'s precedent for a fixed-shape precomputation — that is still correct for a STATIC area profile. This kernel exists for the case the plan's `NeuralPhysiologyState.TractGestureHead` implies: an area profile predicted fresh every control frame (100 Hz per `ARCHITECTURE.md` §3), which would otherwise force a GPU-to-CPU-to-GPU round trip every frame just to run one elementwise division — exactly the per-frame readback stall the plan's Production Contract and `engine/core/gpu/VGPUReadbackQueue.js`-style staged-readback convention are meant to avoid. Keeping the whole `TractGestureHead -> junction_k -> tract_waveguide.js` path on GPU is what \"single owned execution path\" requires once area profiles become per-frame instead of per-utterance. Purely elementwise across `(instance, junction)` pairs — no inter-junction dependency at all (unlike `tract_waveguide.js`'s per-sample recurrence), so this is one thread per output element with no workgroup memory or barriers needed."
      },
      {
        "name": "TRACT_TRANSFER_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "TRACT_TRANSFER_WORKGROUP_SIZE",
        "summary": ""
      },
      {
        "name": "TRACT_TRANSFER_EPSILON",
        "kind": "constant",
        "signature": "TRACT_TRANSFER_EPSILON",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/tract_waveguide",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/tract_waveguide.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/tract_waveguide.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/tract_waveguide.js",
    "sourceHash": "1c9857a4ccdcae11da751c66f3d5172ad52aec375a70248ac33ec45f438f89ca",
    "summary": "tract_waveguide.js — Phase 2 ParticleNN articulatory kernel. Production Kelly-Lochbaum single-delay-per-section digital waveguide vocal tract, generalizing `risk/KellyLochbaumWaveguide.js` (R6)'s proven `createVocalTract().step()` recurrence to run entirely on GPU across a whole chunk of samples and N simultaneous voice/capsule instances. Junction reflection coefficients (`k[i] = (A_i - A_{i+1}) / (A_i + A_{i+1})`) are computed CPU-side by the caller via `risk/KellyLochbaumWaveguide.js`'s `reflectionCoefficients` (a fixed-shape, typically-small computation — same rationale `mel_filterbank.js` gives for precomputing its filter matrix CPU-side rather than reproducing it in WGSL) and passed in as `junction_k`, not recomputed here. Constriction noise injection (`constriction_index` + `constriction_noise`) and the nasal TAP signal (`nasal_tap_index`) ARE implemented, as faithful ports of R6's proven `step()` options — they were deferred when this kernel was first written, but `ParticleTract.js` cannot produce real fricatives or real nasal coupling without them: - `constriction_index >= 0` adds that sample's `constriction_noise` value to the RIGHT-going wave entering junction `constriction_index`, exactly as R6's `if (i === constrictionIndex) incomingRight += constrictionNoiseSample`. Injecting at the junction (rather than mixing noise into `source_flow` at the glottis) is what makes a fricative a fricative: only the cavity IN FRONT of the constriction filters the turbulence. - `nasal_tap_index >= 0` writes `right[i] + left[i]` for that section into the packed output's `nasal_tap` block every sample, giving `nasal_junction.js` an anatomically-meaningful `input_flow` to be driven by. Matching R6 exactly, the tap is a READ-ONLY observation: the oral tract is not itself perturbed by it. That remains a simplification versus a rigorous 3-port scattering junction (R6's own docstring says the same); it is enough for controllable, stable nasal coupling, and the honest limitation is recorded here rather than overstated. Both are opt-in: a negative index disables that feature entirely, so callers that want neither pass `-1` and get bit-identical behavior to this kernel before they existed. ## `radiation_difference` must match what the SOURCE already is Lip radiation is approximately a differentiator, so R6's `step()` ends with `radiated = outputRaw - prevOutputRaw`. That is correct when the excitation is glottal FLOW, which is what R6's `GlottalSource.js` raised-cosine pulse produced. It is WRONG when the excitation is already the flow derivative. `glottal_lf.js` is explicitly the LF glottal flow DERIVATIVE model, so feeding it in and then differencing again yields the SECOND derivative: +6 dB/octave of spurious tilt (audibly reedy/buzzy) and a far spikier waveform, whose large crest factor then drives any downstream limiter into distorting every single pitch pulse. Injecting a derivative source and skipping the difference is what gives the correct radiated pressure, and is why formant synthesizers use the LF derivative directly. `radiation_difference` therefore has to agree with the caller's source: 1.0 for a flow source (R6 parity), 0.0 for `glottal_lf.js`. There is no safe default that suits both, so it is an explicit parameter rather than a built-in assumption. ## `wall_loss`: the tube was perfectly lossless, and that is audible Scattering at a Kelly-Lochbaum junction conserves energy exactly, so with only the glottis and lips absorbing anything, the formants of this tube had very high Q. Real vocal tracts lose energy continuously to yielding walls plus viscous and thermal effects, which is what gives formants their bandwidth (roughly 50-100 Hz for F1, more higher up). A resonator with too little damping rings after every excitation, and a whole tube of them reads as a metallic, synthetic buzz laid over the speech no matter how accurate the formant FREQUENCIES are — accurate centres with no bandwidth still does not sound human. `wall_loss` multiplies each traveling wave once per section per sample, so a wave crossing the whole tract is attenuated `wall_loss^num_sections`. Because it is applied per section rather than once at a boundary, the loss accumulates with distance travelled, which is the correct behaviour for a distributed effect. It is a frequency-INDEPENDENT approximation of a loss that really does grow with frequency; the honest limitation is recorded here rather than overstated, and a one-pole filter per junction would be the refinement. 1.0 restores the lossless tube exactly, which is what the R6 parity test uses. One digital-waveguide step is inherently sequential in time (sample n depends on sample n-1's right/left traveling-wave state), but the N-1 junction updates *within* one sample step are independent of each other (each reads only the previous sample's `right`/`left`, matching R6's `step()` computing a full `nextRight`/`nextLeft` array before committing it) — so this kernel assigns one thread per tube section (`local_invocation_id.x`), keeps the whole tract's traveling-wave state in workgroup memory (double-buffered, since every junction read is the *previous* sample's state), and walks the chunk's samples sequentially with a `workgroupBarrier()` between samples. `workgroup_id.x` selects which of up to `num_instances` simultaneous tracts to process, matching every other Phase 2 kernel's per-instance batching convention (`breath_reservoir.js`, `lip_radiation.js`). `MAX_SECTIONS = 64` is a compile-time workgroup-memory cap, comfortably above R6's proven `NUM_SECTIONS = 32` oral / `NASAL_SECTIONS = 16` nasal tract discretizations (`risk/VoiceBoxWaveguideProbe.js`) — `num_sections` over this cap is a caller error, not a silently-truncated tract. To keep this kernel within the project's \"N read-only storage inputs then one read_write storage output\" binding convention (`KernelRegistry.bindGroupLayoutEntriesFor`), all four pieces of kernel-produced data (`radiated_out`, the two persisted traveling-wave state arrays, and the persisted previous-lip-output scalar needed for the next chunk's radiation first-difference) are packed into ONE flat output buffer at fixed uniform-computed offsets, documented in the struct comments below, rather than needing four separate output bindings.",
    "exports": [
      {
        "name": "tractWaveguideOutputLayout",
        "kind": "function",
        "signature": "tractWaveguideOutputLayout(numSections, numSamples, numInstances)",
        "summary": "Compute the flat-output-buffer layout `tract_waveguide.js` writes to and `TractWaveguideModel.js` (Phase 3+ consumer) must read from, given `numSections`/`numSamples`/`numInstances` — kept as one shared function so the WGSL offsets below and any future JS-side reader can never drift apart."
      },
      {
        "name": "tractWaveguideShader",
        "kind": "function",
        "signature": "tractWaveguideShader()",
        "summary": ""
      },
      {
        "name": "TRACT_WAVEGUIDE_MAX_SECTIONS",
        "kind": "constant",
        "signature": "TRACT_WAVEGUIDE_MAX_SECTIONS",
        "summary": "tract_waveguide.js — Phase 2 ParticleNN articulatory kernel. Production Kelly-Lochbaum single-delay-per-section digital waveguide vocal tract, generalizing `risk/KellyLochbaumWaveguide.js` (R6)'s proven `createVocalTract().step()` recurrence to run entirely on GPU across a whole chunk of samples and N simultaneous voice/capsule instances. Junction reflection coefficients (`k[i] = (A_i - A_{i+1}) / (A_i + A_{i+1})`) are computed CPU-side by the caller via `risk/KellyLochbaumWaveguide.js`'s `reflectionCoefficients` (a fixed-shape, typically-small computation — same rationale `mel_filterbank.js` gives for precomputing its filter matrix CPU-side rather than reproducing it in WGSL) and passed in as `junction_k`, not recomputed here. Constriction noise injection (`constriction_index` + `constriction_noise`) and the nasal TAP signal (`nasal_tap_index`) ARE implemented, as faithful ports of R6's proven `step()` options — they were deferred when this kernel was first written, but `ParticleTract.js` cannot produce real fricatives or real nasal coupling without them: - `constriction_index >= 0` adds that sample's `constriction_noise` value to the RIGHT-going wave entering junction `constriction_index`, exactly as R6's `if (i === constrictionIndex) incomingRight += constrictionNoiseSample`. Injecting at the junction (rather than mixing noise into `source_flow` at the glottis) is what makes a fricative a fricative: only the cavity IN FRONT of the constriction filters the turbulence. - `nasal_tap_index >= 0` writes `right[i] + left[i]` for that section into the packed output's `nasal_tap` block every sample, giving `nasal_junction.js` an anatomically-meaningful `input_flow` to be driven by. Matching R6 exactly, the tap is a READ-ONLY observation: the oral tract is not itself perturbed by it. That remains a simplification versus a rigorous 3-port scattering junction (R6's own docstring says the same); it is enough for controllable, stable nasal coupling, and the honest limitation is recorded here rather than overstated. Both are opt-in: a negative index disables that feature entirely, so callers that want neither pass `-1` and get bit-identical behavior to this kernel before they existed. ## `radiation_difference` must match what the SOURCE already is Lip radiation is approximately a differentiator, so R6's `step()` ends with `radiated = outputRaw - prevOutputRaw`. That is correct when the excitation is glottal FLOW, which is what R6's `GlottalSource.js` raised-cosine pulse produced. It is WRONG when the excitation is already the flow derivative. `glottal_lf.js` is explicitly the LF glottal flow DERIVATIVE model, so feeding it in and then differencing again yields the SECOND derivative: +6 dB/octave of spurious tilt (audibly reedy/buzzy) and a far spikier waveform, whose large crest factor then drives any downstream limiter into distorting every single pitch pulse. Injecting a derivative source and skipping the difference is what gives the correct radiated pressure, and is why formant synthesizers use the LF derivative directly. `radiation_difference` therefore has to agree with the caller's source: 1.0 for a flow source (R6 parity), 0.0 for `glottal_lf.js`. There is no safe default that suits both, so it is an explicit parameter rather than a built-in assumption. ## `wall_loss`: the tube was perfectly lossless, and that is audible Scattering at a Kelly-Lochbaum junction conserves energy exactly, so with only the glottis and lips absorbing anything, the formants of this tube had very high Q. Real vocal tracts lose energy continuously to yielding walls plus viscous and thermal effects, which is what gives formants their bandwidth (roughly 50-100 Hz for F1, more higher up). A resonator with too little damping rings after every excitation, and a whole tube of them reads as a metallic, synthetic buzz laid over the speech no matter how accurate the formant FREQUENCIES are — accurate centres with no bandwidth still does not sound human. `wall_loss` multiplies each traveling wave once per section per sample, so a wave crossing the whole tract is attenuated `wall_loss^num_sections`. Because it is applied per section rather than once at a boundary, the loss accumulates with distance travelled, which is the correct behaviour for a distributed effect. It is a frequency-INDEPENDENT approximation of a loss that really does grow with frequency; the honest limitation is recorded here rather than overstated, and a one-pole filter per junction would be the refinement. 1.0 restores the lossless tube exactly, which is what the R6 parity test uses. One digital-waveguide step is inherently sequential in time (sample n depends on sample n-1's right/left traveling-wave state), but the N-1 junction updates *within* one sample step are independent of each other (each reads only the previous sample's `right`/`left`, matching R6's `step()` computing a full `nextRight`/`nextLeft` array before committing it) — so this kernel assigns one thread per tube section (`local_invocation_id.x`), keeps the whole tract's traveling-wave state in workgroup memory (double-buffered, since every junction read is the *previous* sample's state), and walks the chunk's samples sequentially with a `workgroupBarrier()` between samples. `workgroup_id.x` selects which of up to `num_instances` simultaneous tracts to process, matching every other Phase 2 kernel's per-instance batching convention (`breath_reservoir.js`, `lip_radiation.js`). `MAX_SECTIONS = 64` is a compile-time workgroup-memory cap, comfortably above R6's proven `NUM_SECTIONS = 32` oral / `NASAL_SECTIONS = 16` nasal tract discretizations (`risk/VoiceBoxWaveguideProbe.js`) — `num_sections` over this cap is a caller error, not a silently-truncated tract. To keep this kernel within the project's \"N read-only storage inputs then one read_write storage output\" binding convention (`KernelRegistry.bindGroupLayoutEntriesFor`), all four pieces of kernel-produced data (`radiated_out`, the two persisted traveling-wave state arrays, and the persisted previous-lip-output scalar needed for the next chunk's radiation first-difference) are packed into ONE flat output buffer at fixed uniform-computed offsets, documented in the struct comments below, rather than needing four separate output bindings."
      },
      {
        "name": "TRACT_WAVEGUIDE_ENTRY_POINT",
        "kind": "constant",
        "signature": "TRACT_WAVEGUIDE_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "TRACT_WAVEGUIDE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "TRACT_WAVEGUIDE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/articulatory/visual_state_decimate",
    "path": "agi/reference/particle_voice/nn/kernels/articulatory/visual_state_decimate.md",
    "source": "agi/particle_voice/nn/kernels/articulatory/visual_state_decimate.js",
    "import": "/agi/particle_voice/nn/kernels/articulatory/visual_state_decimate.js",
    "sourceHash": "ac0e231f3e305a5ebf4dedce4addca9b56a5b2ce171fefcb519601b33b5beb36",
    "summary": "visual_state_decimate.js — Phase 2 ParticleNN articulatory kernel. Decimates a high-rate, multi-CHANNEL tract field (e.g. a per-section pressure-like signal, one channel per tube section) down to one box-averaged value per channel per fixed-size frame — the `VisualAirflowState` `ARCHITECTURE.md` §4 names as \"a decimated field for ribbon/particle/heatmap visualization\". Deliberately NOT a reuse of `physiology_feedback_reduce.js` despite the similar per-thread \"small sequential average over a window\" shape: that kernel reduces a single FLAT per-instance signal to (mean, RMS) pairs for model feedback (a statistics reduction); this kernel decimates a multi-CHANNEL per-instance FIELD (extra channel/section axis) down to mean-only per channel per frame, because a rendered airflow ribbon/ heatmap needs a representative value per tube section over time, not an energy statistic, and box-averaging over the decimation window (rather than picking every Nth raw sample) avoids visual aliasing/flicker in the downsampled field — ordinary decimation-filter practice, and consistent with how `overlap_add.js`/`stft.js` already treat windowing as the caller's/kernel's job rather than raw strided subsampling. `ARCHITECTURE.md` §4 also requires that visualization \"must be bit- identically disable-able without changing rendered PCM\" — this kernel reads its input field only (never writes back into it), so skipping this kernel's dispatch entirely when visualization is off cannot affect the `tract_waveguide.js`/`nasal_junction.js` PCM path in any way; that invariant is structural (this kernel has no read_write access to anything the acoustic path also reads), not merely a runtime convention.",
    "exports": [
      {
        "name": "visualStateDecimateShader",
        "kind": "function",
        "signature": "visualStateDecimateShader()",
        "summary": ""
      },
      {
        "name": "VISUAL_STATE_DECIMATE_ENTRY_POINT",
        "kind": "constant",
        "signature": "VISUAL_STATE_DECIMATE_ENTRY_POINT",
        "summary": "visual_state_decimate.js — Phase 2 ParticleNN articulatory kernel. Decimates a high-rate, multi-CHANNEL tract field (e.g. a per-section pressure-like signal, one channel per tube section) down to one box-averaged value per channel per fixed-size frame — the `VisualAirflowState` `ARCHITECTURE.md` §4 names as \"a decimated field for ribbon/particle/heatmap visualization\". Deliberately NOT a reuse of `physiology_feedback_reduce.js` despite the similar per-thread \"small sequential average over a window\" shape: that kernel reduces a single FLAT per-instance signal to (mean, RMS) pairs for model feedback (a statistics reduction); this kernel decimates a multi-CHANNEL per-instance FIELD (extra channel/section axis) down to mean-only per channel per frame, because a rendered airflow ribbon/ heatmap needs a representative value per tube section over time, not an energy statistic, and box-averaging over the decimation window (rather than picking every Nth raw sample) avoids visual aliasing/flicker in the downsampled field — ordinary decimation-filter practice, and consistent with how `overlap_add.js`/`stft.js` already treat windowing as the caller's/kernel's job rather than raw strided subsampling. `ARCHITECTURE.md` §4 also requires that visualization \"must be bit- identically disable-able without changing rendered PCM\" — this kernel reads its input field only (never writes back into it), so skipping this kernel's dispatch entirely when visualization is off cannot affect the `tract_waveguide.js`/`nasal_junction.js` PCM path in any way; that invariant is structural (this kernel has no read_write access to anything the acoustic path also reads), not merely a runtime convention."
      },
      {
        "name": "VISUAL_STATE_DECIMATE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "VISUAL_STATE_DECIMATE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/chunks/complex_math",
    "path": "agi/reference/particle_voice/nn/kernels/chunks/complex_math.md",
    "source": "agi/particle_voice/nn/kernels/chunks/complex_math.js",
    "import": "/agi/particle_voice/nn/kernels/chunks/complex_math.js",
    "sourceHash": "e136ec85b076d78615a3364edf191b53d7ee160f0c0e2a6a5f2267f5bc545e75",
    "summary": "chunks/complex_math.js — Phase 1 ParticleNN kernel chunk. Generic complex-arithmetic WGSL fragment (mul/add/sub/twiddle), split out as reusable infrastructure ahead of Phase 2's `fft/fft1024_f32.js` / `fft/complex_mul.js` kernels — R3's `risk/Fft1024Kernel.js` inlined its own twiddle multiply directly in the butterfly loop (fine for a disposable risk spike); Phase 2's production FFT kernel should compose this chunk instead of re-deriving the same four multiplies/two adds. Complex numbers are represented as `vec2<f32>` (`.x` = real, `.y` = imag) throughout, not a named struct, to keep it directly usable as a return type/local without an extra struct declaration at every call site.",
    "exports": [
      {
        "name": "complexMathChunk",
        "kind": "function",
        "signature": "complexMathChunk()",
        "summary": ""
      },
      {
        "name": "COMPLEX_MATH_CHUNK_NAME",
        "kind": "constant",
        "signature": "COMPLEX_MATH_CHUNK_NAME",
        "summary": "chunks/complex_math.js — Phase 1 ParticleNN kernel chunk. Generic complex-arithmetic WGSL fragment (mul/add/sub/twiddle), split out as reusable infrastructure ahead of Phase 2's `fft/fft1024_f32.js` / `fft/complex_mul.js` kernels — R3's `risk/Fft1024Kernel.js` inlined its own twiddle multiply directly in the butterfly loop (fine for a disposable risk spike); Phase 2's production FFT kernel should compose this chunk instead of re-deriving the same four multiplies/two adds. Complex numbers are represented as `vec2<f32>` (`.x` = real, `.y` = imag) throughout, not a named struct, to keep it directly usable as a return type/local without an extra struct declaration at every call site."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/chunks/f16_utils",
    "path": "agi/reference/particle_voice/nn/kernels/chunks/f16_utils.md",
    "source": "agi/particle_voice/nn/kernels/chunks/f16_utils.js",
    "import": "/agi/particle_voice/nn/kernels/chunks/f16_utils.js",
    "sourceHash": "b0903d02074303a483db5686dc32a281895332f64a2a9da859b54750c7704d2b",
    "summary": "chunks/f16_utils.js — Phase 1 ParticleNN kernel chunk. On the WGSL side, `f16` kernels use WGSL's native `f16` type directly (`enable f16;` + `f16`/`vec2h` etc.) when `shader-f16` is available — no bit-manipulation WGSL fragment is needed there, unlike `int8_pack.js`'s manual unpack (WGSL has no native `i8` storage type, but it DOES have a native `f16` type). This file's job is the *host-side* JS<->f16 bit conversion needed to build/verify f16 test vectors and read `ParticleTensor`'s raw `Uint16Array` f16 readback (`ParticleTensor.js`'s `wrapReadbackBuffer` deliberately leaves `f16` tensors as raw bit patterns — this is where they get decoded).",
    "exports": [
      {
        "name": "decodeF16",
        "kind": "function",
        "signature": "decodeF16(bits)",
        "summary": "IEEE 754 binary16 -> JS float64."
      },
      {
        "name": "encodeF16",
        "kind": "function",
        "signature": "encodeF16(value)",
        "summary": "JS float64 -> IEEE 754 binary16 bit pattern (round-to-nearest-even is not implemented precisely — round-half-away-from-zero on the mantissa, adequate for test-vector construction, not for production-quality quantization-aware training)."
      },
      {
        "name": "decodeF16Array",
        "kind": "function",
        "signature": "decodeF16Array(bitsArray)",
        "summary": "Decode a raw f16-bits `Uint16Array` (e.g. from `ParticleTensor.getData()`) into a `Float64Array`."
      },
      {
        "name": "encodeF16Array",
        "kind": "function",
        "signature": "encodeF16Array(values)",
        "summary": "Encode a `Float32Array`/`Float64Array`/plain array into an f16-bits `Uint16Array`, suitable for `ParticleTensor` `data` with `dtype: 'f16'`."
      },
      {
        "name": "F16_ABS_TOLERANCE",
        "kind": "re-export",
        "signature": "F16_ABS_TOLERANCE",
        "summary": ""
      },
      {
        "name": "F16_REL_TOLERANCE",
        "kind": "re-export",
        "signature": "F16_REL_TOLERANCE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/chunks/int8_pack",
    "path": "agi/reference/particle_voice/nn/kernels/chunks/int8_pack.md",
    "source": "agi/particle_voice/nn/kernels/chunks/int8_pack.js",
    "import": "/agi/particle_voice/nn/kernels/chunks/int8_pack.js",
    "sourceHash": "7dc96101978d5d20feffcdda25b6b0c086f10c33fba69f12f381d95d8a78b63f",
    "summary": "chunks/int8_pack.js — Phase 1 ParticleNN kernel chunk. WGSL side: the `unpack_int8` fragment, extracted from R2's `risk/MatmulW8A16Kernel.js` (proven correct there — R2's parity test passed with max abs/rel error ~3.4e-7 against a JS scalar reference) into a shared chunk so `matmul_w8a16.js`/`conv1d_w8a16.js`/ `dequant_int8.js` all use the exact same unpack logic rather than each re-deriving it. JS side: the host-side symmetric block quantizer that produces the `packed`/`scales` pair this WGSL fragment expects, matching `ParticleTensor.js`'s documented `quant` scheme: `value = i8Value * scale` (zero-point-free, one scale per `blockSize`-sized block).",
    "exports": [
      {
        "name": "int8PackChunk",
        "kind": "function",
        "signature": "int8PackChunk()",
        "summary": ""
      },
      {
        "name": "quantizeInt8Blockwise",
        "kind": "function",
        "signature": "quantizeInt8Blockwise(values, blockSize)",
        "summary": "Symmetric per-block INT8 quantization: for each contiguous `blockSize`-sized block of `values`, scale = max(abs(block)) / 127, quantized = round(value / scale). Returns `{ packed: Uint32Array, scales: Float32Array, blockSize }`. `values.length` must be a multiple of 4 (four int8 values pack into one u32, matching R2's little-end-is-lowest-index layout)."
      },
      {
        "name": "dequantizeInt8Blockwise",
        "kind": "function",
        "signature": "dequantizeInt8Blockwise(quantized, scales, blockSize)",
        "summary": "Dequantize `quantizeInt8Blockwise`'s output back to a `Float32Array`, for host-side parity checking against a kernel's GPU output."
      },
      {
        "name": "INT8_PACK_CHUNK_NAME",
        "kind": "constant",
        "signature": "INT8_PACK_CHUNK_NAME",
        "summary": "chunks/int8_pack.js — Phase 1 ParticleNN kernel chunk. WGSL side: the `unpack_int8` fragment, extracted from R2's `risk/MatmulW8A16Kernel.js` (proven correct there — R2's parity test passed with max abs/rel error ~3.4e-7 against a JS scalar reference) into a shared chunk so `matmul_w8a16.js`/`conv1d_w8a16.js`/ `dequant_int8.js` all use the exact same unpack logic rather than each re-deriving it. JS side: the host-side symmetric block quantizer that produces the `packed`/`scales` pair this WGSL fragment expects, matching `ParticleTensor.js`'s documented `quant` scheme: `value = i8Value * scale` (zero-point-free, one scale per `blockSize`-sized block)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/chunks/math_common",
    "path": "agi/reference/particle_voice/nn/kernels/chunks/math_common.md",
    "source": "agi/particle_voice/nn/kernels/chunks/math_common.js",
    "import": "/agi/particle_voice/nn/kernels/chunks/math_common.js",
    "sourceHash": "c3bb5663ea36475ad26d0c6fc280a87c51cc761f9fc3e6fa171b0c4dce9ffedd",
    "summary": "chunks/math_common.js — Phase 1 ParticleNN kernel chunk. Small reusable WGSL scalar-math fragments shared by multiple kernel generators, so e.g. `silu_f16.js` and any future fused-activation kernel define `siluScalar()`/`sigmoidScalar()` exactly once rather than each inlining its own copy (the kind of duplication the R2 spike's inline `unpack_int8` was an acceptable one-off for, but Phase 1 kernels should not repeat).",
    "exports": [
      {
        "name": "mathCommonChunk",
        "kind": "function",
        "signature": "mathCommonChunk()",
        "summary": ""
      },
      {
        "name": "MATH_COMMON_CHUNK_NAME",
        "kind": "constant",
        "signature": "MATH_COMMON_CHUNK_NAME",
        "summary": "chunks/math_common.js — Phase 1 ParticleNN kernel chunk. Small reusable WGSL scalar-math fragments shared by multiple kernel generators, so e.g. `silu_f16.js` and any future fused-activation kernel define `siluScalar()`/`sigmoidScalar()` exactly once rather than each inlining its own copy (the kind of duplication the R2 spike's inline `unpack_int8` was an acceptable one-off for, but Phase 1 kernels should not repeat)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/convolution/conv1d_w8a16",
    "path": "agi/reference/particle_voice/nn/kernels/convolution/conv1d_w8a16.md",
    "source": "agi/particle_voice/nn/kernels/convolution/conv1d_w8a16.js",
    "import": "/agi/particle_voice/nn/kernels/convolution/conv1d_w8a16.js",
    "sourceHash": "a34e557313d05b525a9c7e579f1d27233a09b9d7c7b8519aa1159ba1eececbdf",
    "summary": "convolution/conv1d_w8a16.js — Phase 1 ParticleNN kernel. 1D convolution, INT8 weights (same 4-packed-per-u32 layout and `unpack_int8` as `matmul_w8a16.js`) x FP16/FP32 activations, FP32 accumulation. `[inChannels, kernelWidth]` per output channel, `'same'` padding (input implicitly zero-padded so output length equals input length) — stride 1 only; dilation/stride are Phase 2+ follow-ups if a consumer needs them (R7's toy Conv1D model used stride 1 only, and no Phase -1/0 document commits to anything else yet). Weight layout: `packedWeights[outCh * ceil(inChannels*kernelWidth/4) + k4]`, a u32 whose 4 bytes are int8 weights for the flattened `(inCh, tap)` index `k = k4*4 + 0..3`, where `inCh = k / kernelWidth` and `tap = k % kernelWidth`. `scales[outCh]` is the per-output-channel scale. `inChannels * kernelWidth` must be a multiple of 4.",
    "exports": [
      {
        "name": "conv1dW8a16Shader",
        "kind": "function",
        "signature": "conv1dW8a16Shader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "CONV1D_W8A16_ENTRY_POINT",
        "kind": "constant",
        "signature": "CONV1D_W8A16_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "CONV1D_W8A16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "CONV1D_W8A16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/core/transpose_2d",
    "path": "agi/reference/particle_voice/nn/kernels/core/transpose_2d.md",
    "source": "agi/particle_voice/nn/kernels/core/transpose_2d.js",
    "import": "/agi/particle_voice/nn/kernels/core/transpose_2d.js",
    "sourceHash": "6b7fd18cd76b62f07db290eadb286780e8201d2dfa70c581682412d8716b93be",
    "summary": "core/transpose_2d.js — Phase 3 ParticleNN kernel. 2D transpose: `out[c, r] = in[r, c]`, i.e. `[rows, cols]` → `[cols, rows]`, both row-major. ## Why this exists The Phase 1 kernels disagree about which axis is major, and building `PhonemeEncoder.js` is the first time anything composes them: - `speech/embedding_f16.js` writes `[numIds, embedDim]` — **sequence-major** (one row per token), the natural shape for a gather. - `convolution/conv1d_w8a16.js` reads `[inChannels, seqLen]` — **channel-major**, the natural shape for sliding a kernel along time. - `normalization/rmsnorm_f16.js` normalizes each ROW independently, so per-token normalization over the feature axis needs sequence-major again. Neither layout is wrong for its own kernel, but the encoder has to cross between them, and nothing in the registry could. Rather than duplicating a transposed variant of the convolution (or silently re-interpreting a buffer and producing scrambled activations), the axis swap is its own explicit, testable op. Deliberately naive: one thread per element, no tiling and no shared-memory staging. The encoder's tensors are small (tens of channels x hundreds of frames), so the uncoalesced write is not worth the complexity — and a tiled version can replace this later without changing a single caller, since the contract is just \"swap the axes\".",
    "exports": [
      {
        "name": "transpose2dShader",
        "kind": "function",
        "signature": "transpose2dShader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "TRANSPOSE_2D_ENTRY_POINT",
        "kind": "constant",
        "signature": "TRANSPOSE_2D_ENTRY_POINT",
        "summary": "core/transpose_2d.js — Phase 3 ParticleNN kernel. 2D transpose: `out[c, r] = in[r, c]`, i.e. `[rows, cols]` → `[cols, rows]`, both row-major. ## Why this exists The Phase 1 kernels disagree about which axis is major, and building `PhonemeEncoder.js` is the first time anything composes them: - `speech/embedding_f16.js` writes `[numIds, embedDim]` — **sequence-major** (one row per token), the natural shape for a gather. - `convolution/conv1d_w8a16.js` reads `[inChannels, seqLen]` — **channel-major**, the natural shape for sliding a kernel along time. - `normalization/rmsnorm_f16.js` normalizes each ROW independently, so per-token normalization over the feature axis needs sequence-major again. Neither layout is wrong for its own kernel, but the encoder has to cross between them, and nothing in the registry could. Rather than duplicating a transposed variant of the convolution (or silently re-interpreting a buffer and producing scrambled activations), the axis swap is its own explicit, testable op. Deliberately naive: one thread per element, no tiling and no shared-memory staging. The encoder's tensors are small (tens of channels x hundreds of frames), so the uncoalesced write is not worth the complexity — and a tiled version can replace this later without changing a single caller, since the contract is just \"swap the axes\"."
      },
      {
        "name": "TRANSPOSE_2D_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "TRANSPOSE_2D_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/fft/complex_mul",
    "path": "agi/reference/particle_voice/nn/kernels/fft/complex_mul.md",
    "source": "agi/particle_voice/nn/kernels/fft/complex_mul.js",
    "import": "/agi/particle_voice/nn/kernels/fft/complex_mul.js",
    "sourceHash": "3eb4e24f1739e1be87d89b2caa71a480dfafd00c7a9ef72e3d16ae21b642ae22",
    "summary": "complex_mul.js — Phase 2 ParticleNN DSP kernel. Elementwise complex multiplication: `(a+bi)(c+di) = (ac-bd) + (ad+bc)i`, over interleaved `[re, im, re, im, ...]` buffers, matching `fft1024_f32.js`'s interleaved-complex layout so FFT output can feed this directly (e.g. frequency-domain filtering: `FFT(x) * H` then `IFFT`).",
    "exports": [
      {
        "name": "complexMulShader",
        "kind": "function",
        "signature": "complexMulShader()",
        "summary": ""
      },
      {
        "name": "COMPLEX_MUL_ENTRY_POINT",
        "kind": "constant",
        "signature": "COMPLEX_MUL_ENTRY_POINT",
        "summary": "complex_mul.js — Phase 2 ParticleNN DSP kernel. Elementwise complex multiplication: `(a+bi)(c+di) = (ac-bd) + (ad+bc)i`, over interleaved `[re, im, re, im, ...]` buffers, matching `fft1024_f32.js`'s interleaved-complex layout so FFT output can feed this directly (e.g. frequency-domain filtering: `FFT(x) * H` then `IFFT`)."
      },
      {
        "name": "COMPLEX_MUL_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "COMPLEX_MUL_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/fft/fft1024_f32",
    "path": "agi/reference/particle_voice/nn/kernels/fft/fft1024_f32.md",
    "source": "agi/particle_voice/nn/kernels/fft/fft1024_f32.js",
    "import": "/agi/particle_voice/nn/kernels/fft/fft1024_f32.js",
    "sourceHash": "371829d47fd3b9f85b62f0eee545ae270a10e5834e98acbf9de2ede297768fd5",
    "summary": "fft1024_f32.js — Phase 2 ParticleNN DSP kernel. Single-workgroup, in-workgroup-memory, iterative radix-2 Cooley-Tukey forward FFT — generalizes `risk/Fft1024Kernel.js` (R3)'s proven butterfly network to run with **256 threads/workgroup** (each thread handles 2 butterfly operations per stage / 4 elements for load-scatter) instead of R3's 512, so it needs **no elevated `maxComputeWorkgroupSizeX` request** — every conformant WebGPU adapter guarantees 256 by default. This resolves the exact gap `spec/DeviceContract-v0.md` §4 flagged as an unresolved \"Phase 1/2 follow-up\": rather than requesting a higher limit with a fallback path for adapters that refuse it, this kernel simply never needs the higher limit. One workgroup computes exactly one 1024-point complex FFT; `workgroup_id.x` selects which frame of a batch to process (`dispatchWorkgroups(numFrames)`), matching R3's batching design. Complex numbers are interleaved `[re0, im0, re1, im1, ...]` in a single flat `array<f32>` per frame (`frame_base = wid.x * N * 2`) — one input buffer, one output buffer, matching every other Phase 1/2 kernel's single-input/single-output convention (unlike R3's separate real/imag arrays). No uniform buffer is needed (frame selection comes entirely from `workgroup_id`), so this kernel registers with `hasUniform: false` in `KernelRegistry.js`.",
    "exports": [
      {
        "name": "buildFft1024Shader",
        "kind": "function",
        "signature": "buildFft1024Shader(direction)",
        "summary": "Shared by `fft1024_f32.js` and `ifft1024_f32.js` — `direction` (+1 forward, -1 inverse) is baked in at generation time, not read from a uniform, since each op is a distinct, fixed-behavior kernel."
      },
      {
        "name": "fft1024F32Shader",
        "kind": "function",
        "signature": "fft1024F32Shader()",
        "summary": ""
      },
      {
        "name": "FFT_N",
        "kind": "constant",
        "signature": "FFT_N",
        "summary": "fft1024_f32.js — Phase 2 ParticleNN DSP kernel. Single-workgroup, in-workgroup-memory, iterative radix-2 Cooley-Tukey forward FFT — generalizes `risk/Fft1024Kernel.js` (R3)'s proven butterfly network to run with **256 threads/workgroup** (each thread handles 2 butterfly operations per stage / 4 elements for load-scatter) instead of R3's 512, so it needs **no elevated `maxComputeWorkgroupSizeX` request** — every conformant WebGPU adapter guarantees 256 by default. This resolves the exact gap `spec/DeviceContract-v0.md` §4 flagged as an unresolved \"Phase 1/2 follow-up\": rather than requesting a higher limit with a fallback path for adapters that refuse it, this kernel simply never needs the higher limit. One workgroup computes exactly one 1024-point complex FFT; `workgroup_id.x` selects which frame of a batch to process (`dispatchWorkgroups(numFrames)`), matching R3's batching design. Complex numbers are interleaved `[re0, im0, re1, im1, ...]` in a single flat `array<f32>` per frame (`frame_base = wid.x * N * 2`) — one input buffer, one output buffer, matching every other Phase 1/2 kernel's single-input/single-output convention (unlike R3's separate real/imag arrays). No uniform buffer is needed (frame selection comes entirely from `workgroup_id`), so this kernel registers with `hasUniform: false` in `KernelRegistry.js`."
      },
      {
        "name": "FFT_LOG2N",
        "kind": "constant",
        "signature": "FFT_LOG2N",
        "summary": ""
      },
      {
        "name": "FFT_WORKGROUP_THREADS",
        "kind": "constant",
        "signature": "FFT_WORKGROUP_THREADS",
        "summary": ""
      },
      {
        "name": "FFT_ENTRY_POINT",
        "kind": "constant",
        "signature": "FFT_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "FFT_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "FFT_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/fft/ifft1024_f32",
    "path": "agi/reference/particle_voice/nn/kernels/fft/ifft1024_f32.md",
    "source": "agi/particle_voice/nn/kernels/fft/ifft1024_f32.js",
    "import": "/agi/particle_voice/nn/kernels/fft/ifft1024_f32.js",
    "sourceHash": "22733db23e3f93bd6b455a5dc77e3308f377537ec606c8fe44d648b664fa4aa8",
    "summary": "ifft1024_f32.js — Phase 2 ParticleNN DSP kernel. Inverse of `fft1024_f32.js`: same butterfly network (see that file's docstring for the full algorithm/limit-generalization rationale), with `direction = -1` baked in — conjugate the input, run the same forward butterfly network, conjugate + scale by `1/N` (`IFFT(X) = (1/N) * conj(FFT(conj(X)))`), matching R3's proven identity.",
    "exports": [
      {
        "name": "ifft1024F32Shader",
        "kind": "function",
        "signature": "ifft1024F32Shader()",
        "summary": ""
      },
      {
        "name": "FFT_ENTRY_POINT",
        "kind": "re-export",
        "signature": "FFT_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "FFT_LOG2N",
        "kind": "re-export",
        "signature": "FFT_LOG2N",
        "summary": ""
      },
      {
        "name": "FFT_N",
        "kind": "re-export",
        "signature": "FFT_N",
        "summary": ""
      },
      {
        "name": "FFT_WORKGROUP_SIZE",
        "kind": "re-export",
        "signature": "FFT_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/matmul/matmul_w8a16",
    "path": "agi/reference/particle_voice/nn/kernels/matmul/matmul_w8a16.md",
    "source": "agi/particle_voice/nn/kernels/matmul/matmul_w8a16.js",
    "import": "/agi/particle_voice/nn/kernels/matmul/matmul_w8a16.js",
    "sourceHash": "e573221354603c35e438ee83cd17121d00705091e6d472733ae2065c6ce38e18",
    "summary": "matmul/matmul_w8a16.js — Phase 1 ParticleNN kernel. INT8 weights (4-packed per u32, per-output-channel scale, `chunks/int8_pack.js`'s `unpack_int8`) x FP16 activations, FP32 accumulation, FP16 output — the real target this plan names, superseding R2's FP32-activation stand-in (`risk/MatmulW8A16Kernel.js`) now that R1 has proven `shader-f16` is available on at least one reference device. Falls back to FP32 activations/output when `useShaderF16` is false (DeviceContract-v0's `no-f16` tier) — same kernel body, different element type, not two separately-maintained kernels. Weight layout (unchanged from R2, already proven): logical weights are [K, N]; `packedWeights[n * (K/4) + k4]` is a u32 whose 4 bytes are the int8 weights for k = k4*4 + 0..3 (little end = lowest k); `scales[n]` is the per-output-channel dequantization scale. K must be a multiple of 4.",
    "exports": [
      {
        "name": "matmulW8a16Shader",
        "kind": "function",
        "signature": "matmulW8a16Shader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "MATMUL_W8A16_ENTRY_POINT",
        "kind": "constant",
        "signature": "MATMUL_W8A16_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "MATMUL_W8A16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "MATMUL_W8A16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/normalization/rmsnorm_f16",
    "path": "agi/reference/particle_voice/nn/kernels/normalization/rmsnorm_f16.md",
    "source": "agi/particle_voice/nn/kernels/normalization/rmsnorm_f16.js",
    "import": "/agi/particle_voice/nn/kernels/normalization/rmsnorm_f16.js",
    "sourceHash": "598ad539ca4a533d2d0a56a11133457094e133738631d217d01f9e5a35cfe5be",
    "summary": "normalization/rmsnorm_f16.js — Phase 1 ParticleNN kernel. Row-wise RMSNorm: `out[i] = x[i] / sqrt(mean(x^2) + eps) * weight[i]`. One workgroup per row; workgroup-shared reduction for the sum-of-squares (standard tree reduction, `WORKGROUP_SIZE` must be a power of two). Accumulates in f32 regardless of `useShaderF16`, matching `matmul_w8a16.js`'s \"FP32 accumulation\" convention — only the input/output storage element type changes with the feature flag.",
    "exports": [
      {
        "name": "rmsnormF16Shader",
        "kind": "function",
        "signature": "rmsnormF16Shader({ useShaderF16 = false, eps = 1e-6 } = {})",
        "summary": ""
      },
      {
        "name": "RMSNORM_F16_ENTRY_POINT",
        "kind": "constant",
        "signature": "RMSNORM_F16_ENTRY_POINT",
        "summary": "normalization/rmsnorm_f16.js — Phase 1 ParticleNN kernel. Row-wise RMSNorm: `out[i] = x[i] / sqrt(mean(x^2) + eps) * weight[i]`. One workgroup per row; workgroup-shared reduction for the sum-of-squares (standard tree reduction, `WORKGROUP_SIZE` must be a power of two). Accumulates in f32 regardless of `useShaderF16`, matching `matmul_w8a16.js`'s \"FP32 accumulation\" convention — only the input/output storage element type changes with the feature flag."
      },
      {
        "name": "RMSNORM_F16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "RMSNORM_F16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/quantization/dequant_int8",
    "path": "agi/reference/particle_voice/nn/kernels/quantization/dequant_int8.md",
    "source": "agi/particle_voice/nn/kernels/quantization/dequant_int8.js",
    "import": "/agi/particle_voice/nn/kernels/quantization/dequant_int8.js",
    "sourceHash": "7bbca96258783b21efd68b02b9692a392ff0b24392eb92c285e9da9acbcd7ce1",
    "summary": "quantization/dequant_int8.js — Phase 1 ParticleNN kernel. Standalone unpack+dequantize kernel: `matmul_w8a16.js`/`conv1d_w8a16.js` fuse unpack+dequant+multiply-accumulate inline for performance, but `WeightPageUploader.js`/tooling sometimes need a plain materialized dequantized tensor (e.g. for a CPU-side parity check, or a debug visualization) without running a full matmul — this is that kernel. Uses the same `chunks/int8_pack.js` `unpack_int8` and per-block scale scheme as `ParticleTensor.js`'s documented `quant` metadata.",
    "exports": [
      {
        "name": "dequantInt8Shader",
        "kind": "function",
        "signature": "dequantInt8Shader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "DEQUANT_INT8_ENTRY_POINT",
        "kind": "constant",
        "signature": "DEQUANT_INT8_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "DEQUANT_INT8_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "DEQUANT_INT8_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/quantization/pack_unpack",
    "path": "agi/reference/particle_voice/nn/kernels/quantization/pack_unpack.md",
    "source": "agi/particle_voice/nn/kernels/quantization/pack_unpack.js",
    "import": "/agi/particle_voice/nn/kernels/quantization/pack_unpack.js",
    "sourceHash": "90b0f96a8adb1b4a91da43cea0f3defb250926901d802fbe8edde199ec2d4d29",
    "summary": "quantization/pack_unpack.js — Phase 1 ParticleNN kernel. The pack direction complementing `dequant_int8.js`'s unpack direction: a GPU kernel that bit-packs already-quantized int8 values (as `i32` inputs already rounded/clamped to [-127, 127] — computing the *scale* is a separate reduction pass, not this kernel's job) into 4-packed u32 words, matching `chunks/int8_pack.js`'s host-side `quantizeInt8Blockwise`'s packed-word layout exactly (so a GPU-side requantization pipeline and the JS host-side one are interchangeable).",
    "exports": [
      {
        "name": "packInt8Shader",
        "kind": "function",
        "signature": "packInt8Shader()",
        "summary": "Packs `array<i32>` (one already-quantized value per element, [-127,127]) into `array<u32>` (4 values per word). `count` must be a multiple of 4."
      },
      {
        "name": "packInt8Reference",
        "kind": "function",
        "signature": "packInt8Reference(quantizedInt8Array)",
        "summary": "JS-side reference pack, for parity-testing `packInt8Shader` against a CPU implementation. Mirrors `chunks/int8_pack.js`'s packing loop exactly."
      },
      {
        "name": "PACK_INT8_ENTRY_POINT",
        "kind": "constant",
        "signature": "PACK_INT8_ENTRY_POINT",
        "summary": "quantization/pack_unpack.js — Phase 1 ParticleNN kernel. The pack direction complementing `dequant_int8.js`'s unpack direction: a GPU kernel that bit-packs already-quantized int8 values (as `i32` inputs already rounded/clamped to [-127, 127] — computing the *scale* is a separate reduction pass, not this kernel's job) into 4-packed u32 words, matching `chunks/int8_pack.js`'s host-side `quantizeInt8Blockwise`'s packed-word layout exactly (so a GPU-side requantization pipeline and the JS host-side one are interchangeable)."
      },
      {
        "name": "PACK_INT8_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "PACK_INT8_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/crossfade",
    "path": "agi/reference/particle_voice/nn/kernels/speech/crossfade.md",
    "source": "agi/particle_voice/nn/kernels/speech/crossfade.js",
    "import": "/agi/particle_voice/nn/kernels/speech/crossfade.js",
    "sourceHash": "6a6d8a82d15cff5ee5fc0818df6413afe55c7a9a1924fd11ccd7a8c236f9a6f1",
    "summary": "crossfade.js — Phase 2 ParticleNN DSP kernel. Equal-power crossfade between two equal-length signals over their full length: `t = i/(length-1)`, `out = a*cos(t*pi/2) + b*sin(t*pi/2)` — equal-power (not linear) so the perceived loudness stays constant through the fade instead of dipping at the midpoint (linear crossfade's well-known flaw), matching the constant-power crossfade convention `ARCHITECTURE.md`'s reaction-system voice transitions call for.",
    "exports": [
      {
        "name": "crossfadeShader",
        "kind": "function",
        "signature": "crossfadeShader()",
        "summary": ""
      },
      {
        "name": "CROSSFADE_ENTRY_POINT",
        "kind": "constant",
        "signature": "CROSSFADE_ENTRY_POINT",
        "summary": "crossfade.js — Phase 2 ParticleNN DSP kernel. Equal-power crossfade between two equal-length signals over their full length: `t = i/(length-1)`, `out = a*cos(t*pi/2) + b*sin(t*pi/2)` — equal-power (not linear) so the perceived loudness stays constant through the fade instead of dipping at the midpoint (linear crossfade's well-known flaw), matching the constant-power crossfade convention `ARCHITECTURE.md`'s reaction-system voice transitions call for."
      },
      {
        "name": "CROSSFADE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "CROSSFADE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/embedding_f16",
    "path": "agi/reference/particle_voice/nn/kernels/speech/embedding_f16.md",
    "source": "agi/particle_voice/nn/kernels/speech/embedding_f16.js",
    "import": "/agi/particle_voice/nn/kernels/speech/embedding_f16.js",
    "sourceHash": "429357dde15d836e92ad885e48587bdaf14223107ea0287f4acee4ab0c1ca206",
    "summary": "speech/embedding_f16.js — Phase 1 ParticleNN kernel. Row-gather embedding lookup: `output[i, :] = table[ids[i], :]`. Named under `speech/` (not `matmul/`) because its first production consumer is `PhonemeEncoder.js`'s phoneme-ID embedding (Phase 3), looking up rows out of `PhonemeSet-v0`'s fixed 42-entry table — but the kernel itself is generic over table/embedding-dim size. (That count read \"51\" until Phase 3: the spec's own summary line miscounted its vowel tables. `frontend/PhonemeSet.js` is the executable source of truth and exports `PHONEME_COUNT = 42`, which is what a caller should size `tableRows` from rather than any hard-coded literal.) Output layout is `[numIds, embedDim]` — SEQUENCE-major, one row per token. Note that `convolution/conv1d_w8a16.js` expects CHANNEL-major `[inChannels, seqLen]`, so composing the two requires `core/transpose_2d.js` in between; `PhonemeEncoder.js` does exactly that.",
    "exports": [
      {
        "name": "embeddingF16Shader",
        "kind": "function",
        "signature": "embeddingF16Shader({ useShaderF16 = false } = {})",
        "summary": ""
      },
      {
        "name": "EMBEDDING_F16_ENTRY_POINT",
        "kind": "constant",
        "signature": "EMBEDDING_F16_ENTRY_POINT",
        "summary": "speech/embedding_f16.js — Phase 1 ParticleNN kernel. Row-gather embedding lookup: `output[i, :] = table[ids[i], :]`. Named under `speech/` (not `matmul/`) because its first production consumer is `PhonemeEncoder.js`'s phoneme-ID embedding (Phase 3), looking up rows out of `PhonemeSet-v0`'s fixed 42-entry table — but the kernel itself is generic over table/embedding-dim size. (That count read \"51\" until Phase 3: the spec's own summary line miscounted its vowel tables. `frontend/PhonemeSet.js` is the executable source of truth and exports `PHONEME_COUNT = 42`, which is what a caller should size `tableRows` from rather than any hard-coded literal.) Output layout is `[numIds, embedDim]` — SEQUENCE-major, one row per token. Note that `convolution/conv1d_w8a16.js` expects CHANNEL-major `[inChannels, seqLen]`, so composing the two requires `core/transpose_2d.js` in between; `PhonemeEncoder.js` does exactly that."
      },
      {
        "name": "EMBEDDING_F16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "EMBEDDING_F16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/harmonic_source",
    "path": "agi/reference/particle_voice/nn/kernels/speech/harmonic_source.md",
    "source": "agi/particle_voice/nn/kernels/speech/harmonic_source.js",
    "import": "/agi/particle_voice/nn/kernels/speech/harmonic_source.js",
    "sourceHash": "e4c63320996de935a002ed90d7ee86a1c84839a087e94e90de90a1686366fd1f",
    "summary": "harmonic_source.js — Phase 2 ParticleNN DSP kernel. Voiced glottal pulse train — the \"LF-lite\" raised-cosine shape proven in `risk/GlottalSource.js` (R6): flow(n) = 0.5*(1-cos(2*pi*cyclePos(n)/openSamples))  for cyclePos(n) < openSamples flow(n) = 0                                           otherwise Unlike R6's per-sample sequential CPU generator (which tracks `cyclePos` as running state, updated one sample at a time), this kernel computes every sample **in closed form and fully in parallel**: `cyclePos(n) = (start_phase + n) mod period_samples`, using an **absolute sample counter** (`start_phase`) the caller persists and advances across chunk boundaries — exactly the \"LF glottal source with absolute counter\" the plan's checklist calls for, and mathematically identical to R6's per-sample recurrence for as long as `period_samples`/`open_samples` hold constant across the dispatched chunk (the same constant-until-the- next-period-boundary assumption R6's own generator already makes). Aspiration noise is NOT mixed in here (unlike R6's spike, which combined both for simplicity) — that is `noise_source.js`'s job, mixed downstream, since the operator allowlist treats voiced and unvoiced excitation as independent ops that a later stage combines.",
    "exports": [
      {
        "name": "harmonicSourceShader",
        "kind": "function",
        "signature": "harmonicSourceShader()",
        "summary": ""
      },
      {
        "name": "HARMONIC_SOURCE_ENTRY_POINT",
        "kind": "constant",
        "signature": "HARMONIC_SOURCE_ENTRY_POINT",
        "summary": "harmonic_source.js — Phase 2 ParticleNN DSP kernel. Voiced glottal pulse train — the \"LF-lite\" raised-cosine shape proven in `risk/GlottalSource.js` (R6): flow(n) = 0.5*(1-cos(2*pi*cyclePos(n)/openSamples))  for cyclePos(n) < openSamples flow(n) = 0                                           otherwise Unlike R6's per-sample sequential CPU generator (which tracks `cyclePos` as running state, updated one sample at a time), this kernel computes every sample **in closed form and fully in parallel**: `cyclePos(n) = (start_phase + n) mod period_samples`, using an **absolute sample counter** (`start_phase`) the caller persists and advances across chunk boundaries — exactly the \"LF glottal source with absolute counter\" the plan's checklist calls for, and mathematically identical to R6's per-sample recurrence for as long as `period_samples`/`open_samples` hold constant across the dispatched chunk (the same constant-until-the- next-period-boundary assumption R6's own generator already makes). Aspiration noise is NOT mixed in here (unlike R6's spike, which combined both for simplicity) — that is `noise_source.js`'s job, mixed downstream, since the operator allowlist treats voiced and unvoiced excitation as independent ops that a later stage combines."
      },
      {
        "name": "HARMONIC_SOURCE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "HARMONIC_SOURCE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/mel_filterbank",
    "path": "agi/reference/particle_voice/nn/kernels/speech/mel_filterbank.md",
    "source": "agi/particle_voice/nn/kernels/speech/mel_filterbank.js",
    "import": "/agi/particle_voice/nn/kernels/speech/mel_filterbank.js",
    "sourceHash": "722bcc86cc8aa8e4f93c0be2d7ef3ca0b4a69f2d4bbf1a15ba1623645d0e3425",
    "summary": "mel_filterbank.js — Phase 2 ParticleNN DSP kernel. Applies a precomputed triangular mel filterbank matrix to a power/ magnitude spectrogram: `mel[f, m] = sum_k filterbank[m, k] * spectrum[f, k]` — a small matmul-shaped reduction, not a general matmul (the filterbank is fixed-shape and typically far smaller than `matmul_w8a16.js`'s tile sizes, so a dedicated one-thread-per-output-element kernel is simpler and avoids forcing an INT8-quantized weight path onto filter coefficients that are naturally small dense f32 matrices, not learned weights). The filterbank matrix itself (triangular mel-scale filters for a given sample rate/`n_mels`/`fmin`/`fmax`) is computed once on the CPU at model-load time — this kernel only applies it.",
    "exports": [
      {
        "name": "melFilterbankShader",
        "kind": "function",
        "signature": "melFilterbankShader()",
        "summary": ""
      },
      {
        "name": "MEL_FILTERBANK_ENTRY_POINT",
        "kind": "constant",
        "signature": "MEL_FILTERBANK_ENTRY_POINT",
        "summary": "mel_filterbank.js — Phase 2 ParticleNN DSP kernel. Applies a precomputed triangular mel filterbank matrix to a power/ magnitude spectrogram: `mel[f, m] = sum_k filterbank[m, k] * spectrum[f, k]` — a small matmul-shaped reduction, not a general matmul (the filterbank is fixed-shape and typically far smaller than `matmul_w8a16.js`'s tile sizes, so a dedicated one-thread-per-output-element kernel is simpler and avoids forcing an INT8-quantized weight path onto filter coefficients that are naturally small dense f32 matrices, not learned weights). The filterbank matrix itself (triangular mel-scale filters for a given sample rate/`n_mels`/`fmin`/`fmax`) is computed once on the CPU at model-load time — this kernel only applies it."
      },
      {
        "name": "MEL_FILTERBANK_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "MEL_FILTERBANK_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/noise_source",
    "path": "agi/reference/particle_voice/nn/kernels/speech/noise_source.md",
    "source": "agi/particle_voice/nn/kernels/speech/noise_source.js",
    "import": "/agi/particle_voice/nn/kernels/speech/noise_source.js",
    "sourceHash": "ee9442eec7b42af87042930c1b84cea804189903a9c738b7410385c2c7201070",
    "summary": "noise_source.js — Phase 2 ParticleNN DSP kernel. Deterministic, chunk-boundary-independent aspiration/turbulence noise — the exact integer hash proven in `risk/GlottalSource.js` (R6)'s `noiseAt(absoluteIndex, seed)` (itself the same technique as `StreamingContinuitySource.js`'s counter-based noise): a two-round Murmur-style avalanche hash of `absoluteIndex ^ seed`, mapped to `[-1, 1)`. Counter-based (not a stateful PRNG stream) so any two chunks covering the same absolute sample index always produce the same noise value, regardless of dispatch order or chunk boundaries — required for glitch-free streaming synthesis.",
    "exports": [
      {
        "name": "noiseSourceShader",
        "kind": "function",
        "signature": "noiseSourceShader()",
        "summary": ""
      },
      {
        "name": "NOISE_SOURCE_ENTRY_POINT",
        "kind": "constant",
        "signature": "NOISE_SOURCE_ENTRY_POINT",
        "summary": "noise_source.js — Phase 2 ParticleNN DSP kernel. Deterministic, chunk-boundary-independent aspiration/turbulence noise — the exact integer hash proven in `risk/GlottalSource.js` (R6)'s `noiseAt(absoluteIndex, seed)` (itself the same technique as `StreamingContinuitySource.js`'s counter-based noise): a two-round Murmur-style avalanche hash of `absoluteIndex ^ seed`, mapped to `[-1, 1)`. Counter-based (not a stateful PRNG stream) so any two chunks covering the same absolute sample index always produce the same noise value, regardless of dispatch order or chunk boundaries — required for glitch-free streaming synthesis."
      },
      {
        "name": "NOISE_SOURCE_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "NOISE_SOURCE_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/overlap_add",
    "path": "agi/reference/particle_voice/nn/kernels/speech/overlap_add.md",
    "source": "agi/particle_voice/nn/kernels/speech/overlap_add.js",
    "import": "/agi/particle_voice/nn/kernels/speech/overlap_add.js",
    "sourceHash": "d8034ad1def3f70176578813a48afd42eb3c50223999e246839a9e060ff454ed",
    "summary": "overlap_add.js — Phase 2 ParticleNN DSP kernel. Synthesis overlap-add: reconstructs a continuous signal from overlapping `frame_size`-length windowed frames at `hop_size` stride, matching `risk/WolaSignals.js`'s proven `overlapAdd` reference. Implemented as a **gather**, not a scatter: each output thread owns exactly one output sample and sums every frame that overlaps it (`f*hop <= t < f*hop + frame_size`) — WGSL's core spec has no `atomic<f32>` add, so a scatter (`output[t] += ...` from many threads across overlapping frames) would race; gathering avoids that entirely with no atomics and no serialized per-frame dispatches needed. `component_stride` is the number of consecutive `f32` components per logical sample in `frames_in`, so this kernel can consume both layouts the pipeline actually produces: - `component_stride = 1` — flat real frames. - `component_stride = 2` — INTERLEAVED COMPLEX frames, which is exactly what `ifft1024_f32.js` emits. Only the REAL component is summed (the imaginary residue of an inverse-transformed real signal is numerical noise), making this the ISTFT synthesis stage directly, with no separate deinterleave kernel needed in between. Without this stride, `frames_in[f*frame_size + n]` reads the interleaved-complex buffer as if it were flat real — silently mixing real and imaginary components of the wrong samples together. That gap was found by the `tests/particle-voice/stft-istft-identity.html` integration test.",
    "exports": [
      {
        "name": "overlapAddShader",
        "kind": "function",
        "signature": "overlapAddShader()",
        "summary": ""
      },
      {
        "name": "OVERLAP_ADD_ENTRY_POINT",
        "kind": "constant",
        "signature": "OVERLAP_ADD_ENTRY_POINT",
        "summary": "overlap_add.js — Phase 2 ParticleNN DSP kernel. Synthesis overlap-add: reconstructs a continuous signal from overlapping `frame_size`-length windowed frames at `hop_size` stride, matching `risk/WolaSignals.js`'s proven `overlapAdd` reference. Implemented as a **gather**, not a scatter: each output thread owns exactly one output sample and sums every frame that overlaps it (`f*hop <= t < f*hop + frame_size`) — WGSL's core spec has no `atomic<f32>` add, so a scatter (`output[t] += ...` from many threads across overlapping frames) would race; gathering avoids that entirely with no atomics and no serialized per-frame dispatches needed. `component_stride` is the number of consecutive `f32` components per logical sample in `frames_in`, so this kernel can consume both layouts the pipeline actually produces: - `component_stride = 1` — flat real frames. - `component_stride = 2` — INTERLEAVED COMPLEX frames, which is exactly what `ifft1024_f32.js` emits. Only the REAL component is summed (the imaginary residue of an inverse-transformed real signal is numerical noise), making this the ISTFT synthesis stage directly, with no separate deinterleave kernel needed in between. Without this stride, `frames_in[f*frame_size + n]` reads the interleaved-complex buffer as if it were flat real — silently mixing real and imaginary components of the wrong samples together. That gap was found by the `tests/particle-voice/stft-istft-identity.html` integration test."
      },
      {
        "name": "OVERLAP_ADD_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "OVERLAP_ADD_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/stft",
    "path": "agi/reference/particle_voice/nn/kernels/speech/stft.md",
    "source": "agi/particle_voice/nn/kernels/speech/stft.js",
    "import": "/agi/particle_voice/nn/kernels/speech/stft.js",
    "sourceHash": "2f1a0d7b155a23e65188b6f88b2782829ab86ed7cd51e9fed95c79fb5439d661",
    "summary": "stft.js — Phase 2 ParticleNN DSP kernel. The framing half of STFT: slices overlapping `frame_size`-length windows from a flat mono signal at `hop_size` stride (zero-padded past the signal's end), matching `risk/WolaSignals.js`'s proven `sliceFrames` reference exactly. Writes each frame's samples into the real channel of an interleaved-complex output (`fft1024_f32.js`'s layout) with the imaginary channel zeroed, so the output can be windowed (`window.js`) then fed directly to `fft1024_f32.js` — the actual FFT transform is a separate op (already in the operator allowlist), not duplicated here.",
    "exports": [
      {
        "name": "stftShader",
        "kind": "function",
        "signature": "stftShader()",
        "summary": ""
      },
      {
        "name": "STFT_ENTRY_POINT",
        "kind": "constant",
        "signature": "STFT_ENTRY_POINT",
        "summary": "stft.js — Phase 2 ParticleNN DSP kernel. The framing half of STFT: slices overlapping `frame_size`-length windows from a flat mono signal at `hop_size` stride (zero-padded past the signal's end), matching `risk/WolaSignals.js`'s proven `sliceFrames` reference exactly. Writes each frame's samples into the real channel of an interleaved-complex output (`fft1024_f32.js`'s layout) with the imaginary channel zeroed, so the output can be windowed (`window.js`) then fed directly to `fft1024_f32.js` — the actual FFT transform is a separate op (already in the operator allowlist), not duplicated here."
      },
      {
        "name": "STFT_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "STFT_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/kernels/speech/window",
    "path": "agi/reference/particle_voice/nn/kernels/speech/window.md",
    "source": "agi/particle_voice/nn/kernels/speech/window.js",
    "import": "/agi/particle_voice/nn/kernels/speech/window.js",
    "sourceHash": "ed9150e02394465ffea4789da4d3ed8b7d85d202fce393e562302d25b830f603",
    "summary": "window.js — Phase 2 ParticleNN DSP kernel. Applies a Hann window in-place across framed audio: `out[f,n] = in[f,n] * 0.5*(1-cos(2*pi*n/(N-1)))`, matching `risk/WolaSignals.js`'s proven `hannWindow`/`applyWindow` CPU reference exactly (same coefficient formula) so R3's WOLA identity tests remain the ground truth this kernel is checked against. The coefficient is computed in-shader from the frame-relative index rather than read from a precomputed buffer — one fewer buffer to manage, and Hann's formula is cheap per-element. `stride` is the number of consecutive `f32` components per logical sample, so the same kernel windows both layouts this pipeline actually produces: - `stride = 1` — flat real frames (`[f0s0, f0s1, ...]`). - `stride = 2` — INTERLEAVED COMPLEX frames (`[re, im, re, im, ...]`), which is exactly what `stft.js` emits and what `fft1024_f32.js` consumes. Both the real and imaginary component of a sample get the SAME coefficient (the correct definition of windowing a complex signal), because `n` is derived from the logical sample index `i / stride`, not the raw component index. Without this stride, `n = i % frame_size` silently mis-assigns coefficients when handed interleaved-complex data (component index and sample index diverge by a factor of 2), so `stft.js`'s documented \"output can be windowed (window.js) then fed directly to fft1024_f32.js\" composition would produce a wrong window. That gap was found by the `tests/particle-voice/stft-istft-identity.html` integration test — each kernel passed its own parity test in isolation, but the composed WOLA round-trip did not reconstruct.",
    "exports": [
      {
        "name": "windowShader",
        "kind": "function",
        "signature": "windowShader()",
        "summary": ""
      },
      {
        "name": "WINDOW_ENTRY_POINT",
        "kind": "constant",
        "signature": "WINDOW_ENTRY_POINT",
        "summary": "window.js — Phase 2 ParticleNN DSP kernel. Applies a Hann window in-place across framed audio: `out[f,n] = in[f,n] * 0.5*(1-cos(2*pi*n/(N-1)))`, matching `risk/WolaSignals.js`'s proven `hannWindow`/`applyWindow` CPU reference exactly (same coefficient formula) so R3's WOLA identity tests remain the ground truth this kernel is checked against. The coefficient is computed in-shader from the frame-relative index rather than read from a precomputed buffer — one fewer buffer to manage, and Hann's formula is cheap per-element. `stride` is the number of consecutive `f32` components per logical sample, so the same kernel windows both layouts this pipeline actually produces: - `stride = 1` — flat real frames (`[f0s0, f0s1, ...]`). - `stride = 2` — INTERLEAVED COMPLEX frames (`[re, im, re, im, ...]`), which is exactly what `stft.js` emits and what `fft1024_f32.js` consumes. Both the real and imaginary component of a sample get the SAME coefficient (the correct definition of windowing a complex signal), because `n` is derived from the logical sample index `i / stride`, not the raw component index. Without this stride, `n = i % frame_size` silently mis-assigns coefficients when handed interleaved-complex data (component index and sample index diverge by a factor of 2), so `stft.js`'s documented \"output can be windowed (window.js) then fed directly to fft1024_f32.js\" composition would produce a wrong window. That gap was found by the `tests/particle-voice/stft-istft-identity.html` integration test — each kernel passed its own parity test in isolation, but the composed WOLA round-trip did not reconstruct."
      },
      {
        "name": "WINDOW_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "WINDOW_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/ActivationArena",
    "path": "agi/reference/particle_voice/nn/memory/ActivationArena.md",
    "source": "agi/particle_voice/nn/memory/ActivationArena.js",
    "import": "/agi/particle_voice/nn/memory/ActivationArena.js",
    "sourceHash": "eac1b45ac84a89abb447f9b30ed89aa5ca5bbe4e83f48a03039097dd690d0d0c",
    "summary": "ActivationArena.js — Phase 1 ParticleNN memory. Transient GPU-buffer pool for activation tensors, keyed by byte size: `acquire(tensorName, byteLength)` reuses a same-size buffer already released by an earlier tensor instead of allocating a new one, and `release(tensorName)` (driven by `TensorLifetimePlanner.freedAfter()`) returns a buffer to the pool the moment its last consumer has run.",
    "exports": [
      {
        "name": "ActivationArena",
        "kind": "class",
        "signature": "class ActivationArena",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/GradientArena",
    "path": "agi/reference/particle_voice/nn/memory/GradientArena.md",
    "source": "agi/particle_voice/nn/memory/GradientArena.js",
    "import": "/agi/particle_voice/nn/memory/GradientArena.js",
    "sourceHash": "8d1d44f0935d341b7e23d69e91cda856d6f1e8f909417d953dd2a5593c0ab6d4",
    "summary": "GradientArena.js — Phase 1 ParticleNN memory. Transient GPU-buffer pool for gradient tensors during backward passes — identical pooling mechanism to `ActivationArena.js` (same size-bucketed acquire/release), given its own class identity/file per the plan's checklist rather than duplicated logic, since forward-pass activation buffers and backward-pass gradient buffers have separate lifetimes and should never be pooled together (an activation still needed for a backward computation must not be silently reused as a gradient buffer). Not wired into any executor until Phase 11 (autodiff), per the plan.",
    "exports": [
      {
        "name": "GradientArena",
        "kind": "class",
        "signature": "class GradientArena extends ActivationArena",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/TensorLifetimePlanner",
    "path": "agi/reference/particle_voice/nn/memory/TensorLifetimePlanner.md",
    "source": "agi/particle_voice/nn/memory/TensorLifetimePlanner.js",
    "import": "/agi/particle_voice/nn/memory/TensorLifetimePlanner.js",
    "sourceHash": "bd72d9692af552d764704ccd9c39a049bba77431539f6f6a19ed2e1c7fc67330",
    "summary": "TensorLifetimePlanner.js — Phase 1 ParticleNN memory. First/last-use analysis over a `ParticleProgram`'s step order: for every tensor name, which step produces it (or `-1` if it's an external input to the program) and which step is the last to read it. `ActivationArena.js`/ `GradientArena.js` use `freedAfter(stepIndex)` to know exactly when a buffer becomes free to hand to the next tensor that needs one, enabling buffer aliasing instead of one allocation per tensor per run.",
    "exports": [
      {
        "name": "TensorLifetimePlanner",
        "kind": "class",
        "signature": "class TensorLifetimePlanner",
        "summary": "TensorLifetimePlanner.js — Phase 1 ParticleNN memory. First/last-use analysis over a `ParticleProgram`'s step order: for every tensor name, which step produces it (or `-1` if it's an external input to the program) and which step is the last to read it. `ActivationArena.js`/ `GradientArena.js` use `freedAfter(stepIndex)` to know exactly when a buffer becomes free to hand to the next tensor that needs one, enabling buffer aliasing instead of one allocation per tensor per run."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/WeightBindWindow",
    "path": "agi/reference/particle_voice/nn/memory/WeightBindWindow.md",
    "source": "agi/particle_voice/nn/memory/WeightBindWindow.js",
    "import": "/agi/particle_voice/nn/memory/WeightBindWindow.js",
    "sourceHash": "5385ec8f358658553731ac75f55163cb44af8ad2ddbcb022af55bd8f20766561",
    "summary": "WeightBindWindow.js — Phase 1 ParticleNN memory. Storage-buffer bind group for a program segment's resident weight pages (up to 16 pages, per the plan's \"never allocate one monolithic buffer over 128 MiB\" rule) — one GPU buffer per page (not one big buffer for the whole window), matching `WeightPageStore.js`'s per-page granularity.",
    "exports": [
      {
        "name": "WeightBindWindow",
        "kind": "class",
        "signature": "class WeightBindWindow",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/WeightPageStore",
    "path": "agi/reference/particle_voice/nn/memory/WeightPageStore.md",
    "source": "agi/particle_voice/nn/memory/WeightPageStore.js",
    "import": "/agi/particle_voice/nn/memory/WeightPageStore.js",
    "sourceHash": "10d30c53acf5bdc1d72c5eafe213b77d08a60a2bebbba276ffd3d686a93b1e13",
    "summary": "WeightPageStore.js — Phase 1 ParticleNN memory. Logical 16–32 MiB weight-page manifest: a page = a contiguous tensor set with its own SHA-256 hash, matching `PRNN-v0.md` §6's directory shape exactly (`{ index, byteLength, offset, hash }`) — the EXACT format R8 (`risk/WeightPagingProbe.js`) already proved works for interrupted downloads (retry, not silent data loss) and corruption detection (hash mismatch actually caught, not tautological). Network fetch is injected (`fetchPage(pageIndex, directoryEntry) => Promise<ArrayBuffer|TypedArray>`) rather than hardcoded to `fetch()`, so this store is testable with a deterministic fake fetcher (as R8 was) and still usable with a real HTTP fetch in production without a second implementation.",
    "exports": [
      {
        "name": "WeightPageStore",
        "kind": "class",
        "signature": "class WeightPageStore",
        "summary": ""
      },
      {
        "name": "buildPageDirectory",
        "kind": "function",
        "signature": "async buildPageDirectory(pages)",
        "summary": "Build a `PRNN-v0` §6-shaped directory from page byte data, computing each page's real hash and cumulative byte offset."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/WeightPageUploader",
    "path": "agi/reference/particle_voice/nn/memory/WeightPageUploader.md",
    "source": "agi/particle_voice/nn/memory/WeightPageUploader.js",
    "import": "/agi/particle_voice/nn/memory/WeightPageUploader.js",
    "sourceHash": "7435c2d6f05cbf9de1c754623ffa329a6aa7a0bcbca522af8705bce2bdcb3a6b",
    "summary": "WeightPageUploader.js — Phase 1 ParticleNN memory. Orchestrates `WeightPageStore.js` (download+hash-verify) and `WeightBindWindow.js` (GPU upload) together: priority-ordered upload, resumability (skip already-uploaded pages), and device-loss re-upload — the exact three properties R8 (`risk/WeightPagingProbe.js`) proved for a single/multi-page toy case, generalized here to an arbitrary page count and caller-chosen priority order.",
    "exports": [
      {
        "name": "WeightPageUploader",
        "kind": "class",
        "signature": "class WeightPageUploader",
        "summary": ""
      },
      {
        "name": "attachDeviceLossRecovery",
        "kind": "function",
        "signature": "attachDeviceLossRecovery(device, uploader, recreateDevice, { maxBackoffMs = 8000 } = {})",
        "summary": "Wire the `device.lost` recovery contract from `DeviceContract-v0.md` §5: skip recovery if `reason === 'destroyed'` (intentional disposal, not a crash); otherwise recreate the device with exponential backoff and call `uploader.reuploadAfterDeviceLoss(newDevice)`."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/memory/WeightResidencyPlan",
    "path": "agi/reference/particle_voice/nn/memory/WeightResidencyPlan.md",
    "source": "agi/particle_voice/nn/memory/WeightResidencyPlan.js",
    "import": "/agi/particle_voice/nn/memory/WeightResidencyPlan.js",
    "sourceHash": "ea04fadf8b5ac2e88563b68ba19fbff2a0eae666f0ebb2b124c0a9a9465831cf",
    "summary": "WeightResidencyPlan.js — Phase 1 ParticleNN memory. For each program segment (from a `PRNN-v0.md` §7 bind-window directory: `{ windows: [{ segmentId, pageIndices, bindGroupLayoutHash }] }`), which pages must be resident — plus the diff between two segments' page sets, so a scheduler can decide exactly which pages to load/evict when moving from one segment to the next instead of reloading everything.",
    "exports": [
      {
        "name": "WeightResidencyPlan",
        "kind": "class",
        "signature": "class WeightResidencyPlan",
        "summary": "WeightResidencyPlan.js — Phase 1 ParticleNN memory. For each program segment (from a `PRNN-v0.md` §7 bind-window directory: `{ windows: [{ segmentId, pageIndices, bindGroupLayoutHash }] }`), which pages must be resident — plus the diff between two segments' page sets, so a scheduler can decide exactly which pages to load/evict when moving from one segment to the next instead of reloading everything."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/DeviceProfile",
    "path": "agi/reference/particle_voice/nn/runtime/DeviceProfile.md",
    "source": "agi/particle_voice/nn/runtime/DeviceProfile.js",
    "import": "/agi/particle_voice/nn/runtime/DeviceProfile.js",
    "sourceHash": "31c8954ef49265dc1501c24513283f86b700e9533fe31ec7db3e31157bb3bc05",
    "summary": "DeviceProfile.js — Phase 1 ParticleNN runtime. Capability/tier detection, implementing exactly the tier rules frozen in `spec/DeviceContract-v0.md` §1–§2 (hard requirements + `full`/`no-f16`/ `unsupported` tiers) — not a new policy invented here. Reuses the engine's own WebGPU acquisition (`engine/core/gpu/GpuDevice.js`) rather than reimplementing adapter/device request logic, same as R1's `risk/DeviceMatrixProbe.js`.",
    "exports": [
      {
        "name": "detectDeviceProfile",
        "kind": "function",
        "signature": "async detectDeviceProfile()",
        "summary": "Acquire a throwaway GPUDevice, classify its tier per DeviceContract-v0 §1–§2, then destroy it — mirrors R1's read-only probe discipline. The caller is expected to acquire its OWN long-lived device separately (this function is a classification probe, not a device provider)."
      },
      {
        "name": "DEVICE_TIER",
        "kind": "constant",
        "signature": "DEVICE_TIER",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/KernelValidation",
    "path": "agi/reference/particle_voice/nn/runtime/KernelValidation.md",
    "source": "agi/particle_voice/nn/runtime/KernelValidation.js",
    "import": "/agi/particle_voice/nn/runtime/KernelValidation.js",
    "sourceHash": "385a98aa791b1e1e4f2b0e4ded668e3ba44256cebc973d96161ec0d1f4282c9c",
    "summary": "KernelValidation.js — Phase 1 ParticleNN testing. Reusable dispatch-and-readback plumbing plus the plan's required edge-case test-vector generators (\"odd dims, min dims, zeros, extreme FP16, quantized values\") — kept generic over binding shape rather than one runner-per-kernel, since Phase 1's six kernels all differ in bind-group layout but share the same \"encode uniform + storage buffers, dispatch, read one output buffer back, compare to a JS reference\" shape.",
    "exports": [
      {
        "name": "dispatchAndReadback",
        "kind": "function",
        "signature": "async dispatchAndReadback(device, wgslSource, { uniform, storageInputs = [], outputByteLength, entryPoint = 'main', label = 'KernelValidation' }, workgroups)",
        "summary": "Run one compute dispatch against a set of storage/uniform buffers built from plain typed arrays, then read one designated output buffer back. Compiles `wgslSource` itself (rather than accepting a pre-built pipeline) so it can pair the shader module with the matching explicit bind group layout described above."
      },
      {
        "name": "checkParity",
        "kind": "function",
        "signature": "checkParity(actual, expected, tolerance)",
        "summary": "Compare an actual numeric array to an expected one and return a parity-check result object matching R2's `MatmulBenchProbe.js` shape."
      },
      {
        "name": "randomVector",
        "kind": "function",
        "signature": "randomVector(length, seed = 1)",
        "summary": "Random values in [-1, 1], deterministic given `seed`."
      },
      {
        "name": "zerosVector",
        "kind": "function",
        "signature": "zerosVector(length)",
        "summary": ""
      },
      {
        "name": "extremeF16Vector",
        "kind": "function",
        "signature": "extremeF16Vector(length)",
        "summary": "Values right at/near the extremes a well-behaved f16 encoder/kernel must not silently clip or NaN on."
      },
      {
        "name": "quantizedEdgeVector",
        "kind": "function",
        "signature": "quantizedEdgeVector(length)",
        "summary": "Values already rounded to representable int8 codes, for quantization-path parity tests."
      },
      {
        "name": "EDGE_CASE_DIMENSIONS",
        "kind": "constant",
        "signature": "EDGE_CASE_DIMENSIONS",
        "summary": "Standard edge-case dimension set: odd, minimal (1), and a \"normal\" power-of-two size, for sweeping a kernel's dimension parameter."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/NeuralExecutor",
    "path": "agi/reference/particle_voice/nn/runtime/NeuralExecutor.md",
    "source": "agi/particle_voice/nn/runtime/NeuralExecutor.js",
    "import": "/agi/particle_voice/nn/runtime/NeuralExecutor.js",
    "sourceHash": "ef9bbe66fc96d55c08304075c6b5b97f93a57d069ce12fe5513a6cffe29ffcd6",
    "summary": "NeuralExecutor.js — Phase 1 ParticleNN runtime. Dispatches a compiled `ParticleProgram`'s steps as one command encoder (one compute pass per step, in program order), then submits through the engine's own `GpuFrameBudgetBroker` when one is supplied — sharing the render queue's frame budget rather than a second, uncoordinated submission path. Falls back to a direct `device.queue.submit()` when no broker is given (e.g. an offline/background compile-and-run context with no render loop to share a budget with).",
    "exports": [
      {
        "name": "NeuralExecutor",
        "kind": "class",
        "signature": "class NeuralExecutor",
        "summary": "NeuralExecutor.js — Phase 1 ParticleNN runtime. Dispatches a compiled `ParticleProgram`'s steps as one command encoder (one compute pass per step, in program order), then submits through the engine's own `GpuFrameBudgetBroker` when one is supplied — sharing the render queue's frame budget rather than a second, uncoordinated submission path. Falls back to a direct `device.queue.submit()` when no broker is given (e.g. an offline/background compile-and-run context with no render loop to share a budget with)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/NeuralScheduler",
    "path": "agi/reference/particle_voice/nn/runtime/NeuralScheduler.md",
    "source": "agi/particle_voice/nn/runtime/NeuralScheduler.js",
    "import": "/agi/particle_voice/nn/runtime/NeuralScheduler.js",
    "sourceHash": "2363c74ee062bd1970e7142a890ea71a97e670380464ef8a87bc6513c5fa268a",
    "summary": "NeuralScheduler.js — Phase 1 ParticleNN runtime. Priority-ordered queue of `NeuralExecutor.run()` requests — for when several `ParticleProgram` runs are pending at once (e.g. multiple NPCs queued to speak in the same frame window). Mirrors `engine/core/gpu/AsyncComputeScheduler.js`'s high/normal/low priority bucket design (reference, not imported — ParticleNN stays decoupled from the render-side scheduler's internals) layered on top of `NeuralExecutor.js`, which already defers to a shared `GpuFrameBudgetBroker` for actual frame-budget admission — this file's job is only ordering *which* run gets submitted next, not re-deriving budget admission itself.",
    "exports": [
      {
        "name": "NeuralScheduler",
        "kind": "class",
        "signature": "class NeuralScheduler",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/NumericalTolerance",
    "path": "agi/reference/particle_voice/nn/runtime/NumericalTolerance.md",
    "source": "agi/particle_voice/nn/runtime/NumericalTolerance.js",
    "import": "/agi/particle_voice/nn/runtime/NumericalTolerance.js",
    "sourceHash": "aa27534a4aa10491753d2c31f030baedb968226b0051d21db4d8c672eb76b387",
    "summary": "NumericalTolerance.js — Phase 1 ParticleNN testing. Canonical tolerance constants (single source — `chunks/f16_utils.js` re-exports these rather than defining its own copy) and the shared abs/rel-error comparison used by `KernelValidation.js` and every kernel parity test, matching the exact convention R2's `MatmulBenchProbe.js` already proved out (`maxAbsError`/`maxRelError` against a JS scalar reference).",
    "exports": [
      {
        "name": "maxErrors",
        "kind": "function",
        "signature": "maxErrors(actual, expected)",
        "summary": "Elementwise max abs/rel error between two same-length numeric arrays."
      },
      {
        "name": "withinTolerance",
        "kind": "function",
        "signature": "withinTolerance(actual, expected, { absTolerance, relTolerance })",
        "summary": "True if `maxErrors(actual, expected)` is within `{ absTolerance, relTolerance }` (either bound satisfied per-element is sufficient — matches R2's tolerance semantics)."
      },
      {
        "name": "F16_ABS_TOLERANCE",
        "kind": "constant",
        "signature": "F16_ABS_TOLERANCE",
        "summary": "NumericalTolerance.js — Phase 1 ParticleNN testing. Canonical tolerance constants (single source — `chunks/f16_utils.js` re-exports these rather than defining its own copy) and the shared abs/rel-error comparison used by `KernelValidation.js` and every kernel parity test, matching the exact convention R2's `MatmulBenchProbe.js` already proved out (`maxAbsError`/`maxRelError` against a JS scalar reference)."
      },
      {
        "name": "F16_REL_TOLERANCE",
        "kind": "constant",
        "signature": "F16_REL_TOLERANCE",
        "summary": ""
      },
      {
        "name": "F32_ABS_TOLERANCE",
        "kind": "constant",
        "signature": "F32_ABS_TOLERANCE",
        "summary": ""
      },
      {
        "name": "F32_REL_TOLERANCE",
        "kind": "constant",
        "signature": "F32_REL_TOLERANCE",
        "summary": ""
      },
      {
        "name": "INT8_ABS_TOLERANCE",
        "kind": "constant",
        "signature": "INT8_ABS_TOLERANCE",
        "summary": ""
      },
      {
        "name": "INT8_REL_TOLERANCE",
        "kind": "constant",
        "signature": "INT8_REL_TOLERANCE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/PipelineCache",
    "path": "agi/reference/particle_voice/nn/runtime/PipelineCache.md",
    "source": "agi/particle_voice/nn/runtime/PipelineCache.js",
    "import": "/agi/particle_voice/nn/runtime/PipelineCache.js",
    "sourceHash": "50cf334c904b0186cb1557a0d705696c50062d74cf49c239339a70c010941e48",
    "summary": "PipelineCache.js — Phase 1 ParticleNN runtime. Hash-keyed compute pipeline cache: avoids recompiling the same WGSL source + entry point on every dispatch. Keyed by a cheap string hash of the generated WGSL source (kernels are JS-generated, so byte-identical source for the same params is expected — see `nn/kernels/ShaderComposer.js`), not by kernel name + params (two different param sets can legitimately generate identical WGSL, and caching by source avoids a redundant compile in that case too).",
    "exports": [
      {
        "name": "PipelineCache",
        "kind": "class",
        "signature": "class PipelineCache",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/nn/runtime/PipelineUtils",
    "path": "agi/reference/particle_voice/nn/runtime/PipelineUtils.md",
    "source": "agi/particle_voice/nn/runtime/PipelineUtils.js",
    "import": "/agi/particle_voice/nn/runtime/PipelineUtils.js",
    "sourceHash": "0c1b6e24952617f04e3c5e9573573fae2065095eda080ab65687c90c9a204150",
    "summary": "PipelineUtils.js — Phase 1 ParticleNN runtime. Deliberate byte-for-byte mirror of `agi/llm/kernels/PipelineUtils.js` (design reference, not imported) per the plan's explicit instruction to keep ParticleNN decoupled from the LLM runtime. Already proven correct — every Phase -1 risk spike (R2, R3, R9, etc.) has been using the LLM runtime's copy of this exact logic directly; this is the same logic under ParticleVoice's own module tree.",
    "exports": [
      {
        "name": "createComputePipelineChecked",
        "kind": "function",
        "signature": "async createComputePipelineChecked(device, module, { label, name = label, entryPoint = 'main' } = {})",
        "summary": "PipelineUtils.js — Phase 1 ParticleNN runtime. Deliberate byte-for-byte mirror of `agi/llm/kernels/PipelineUtils.js` (design reference, not imported) per the plan's explicit instruction to keep ParticleNN decoupled from the LLM runtime. Already proven correct — every Phase -1 risk spike (R2, R3, R9, etc.) has been using the LLM runtime's copy of this exact logic directly; this is the same logic under ParticleVoice's own module tree."
      },
      {
        "name": "assertShaderCompiled",
        "kind": "function",
        "signature": "async assertShaderCompiled(module, name = 'shader')",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/ActivityIntegrationProbe",
    "path": "agi/reference/particle_voice/risk/ActivityIntegrationProbe.md",
    "source": "agi/particle_voice/risk/ActivityIntegrationProbe.js",
    "import": "/agi/particle_voice/risk/ActivityIntegrationProbe.js",
    "sourceHash": "5f45a5c33268b24bad3b8fbfded957130d998bf5c63cebfe7dd5bfea83354342",
    "summary": "ActivityIntegrationProbe.js — R15 Phase -1 risk spike orchestrator.",
    "exports": [
      {
        "name": "runActivityIntegrationProbe",
        "kind": "function",
        "signature": "async runActivityIntegrationProbe()",
        "summary": ""
      },
      {
        "name": "buildActivityIntegrationReceipt",
        "kind": "function",
        "signature": "async buildActivityIntegrationReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareActivityIntegrationReceiptDownload",
        "kind": "function",
        "signature": "prepareActivityIntegrationReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R15_ACTIVITY_INTEGRATION_SCHEMA",
        "kind": "constant",
        "signature": "R15_ACTIVITY_INTEGRATION_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/BreathReservoir",
    "path": "agi/reference/particle_voice/risk/BreathReservoir.md",
    "source": "agi/particle_voice/risk/BreathReservoir.js",
    "import": "/agi/particle_voice/risk/BreathReservoir.js",
    "sourceHash": "ffe089a7151a6c9efcf5216328a38ca4ad954711c30c25c3c205903c4603f543",
    "summary": "BreathReservoir.js — R6A Phase -1 risk spike. \"BreathReservoirState (lung pressure, compliance, target subglottal pressure) -> ParticleTract\" — particle-voice-26357c.md, Phase 2. Minimal first-order lag model: lung pressure exponentially approaches whatever target subglottal pressure RespiratoryDriveHead commands, rate set by `compliance` (higher compliance = slower to respond, matching the physical intuition of a more elastic reservoir). This is the mechanism that lets a physiology intervention on breath effort actually change the glottal source's amplitude sample-by-sample rather than jumping instantly.",
    "exports": [
      {
        "name": "createBreathReservoir",
        "kind": "function",
        "signature": "createBreathReservoir({ sampleRate, compliance = 0.15 })",
        "summary": "BreathReservoir.js — R6A Phase -1 risk spike. \"BreathReservoirState (lung pressure, compliance, target subglottal pressure) -> ParticleTract\" — particle-voice-26357c.md, Phase 2. Minimal first-order lag model: lung pressure exponentially approaches whatever target subglottal pressure RespiratoryDriveHead commands, rate set by `compliance` (higher compliance = slower to respond, matching the physical intuition of a more elastic reservoir). This is the mechanism that lets a physiology intervention on breath effort actually change the glottal source's amplitude sample-by-sample rather than jumping instantly."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/CaptureGate",
    "path": "agi/reference/particle_voice/risk/CaptureGate.md",
    "source": "agi/particle_voice/risk/CaptureGate.js",
    "import": "/agi/particle_voice/risk/CaptureGate.js",
    "sourceHash": "7366a995a49542c91c20e99483438c6071e8d4540ca4bf8c1bb0d2b25707a86e",
    "summary": "CaptureGate.js — R14 Phase -1 risk spike. \"Custom-voice capture rejects insufficient speech, low SNR, clipping, echo, music, another speaker, gain drift, device changes, dropped samples, silence, or poor coverage.\" — Phase -1. Each check is a cheap, independently testable heuristic proxy (not a trained classifier) over a session of R10 `ParticleEnergyMeter` frame analyses plus out-of-band capture metadata (device-change/dropped-sample counts, target-phrase coverage) that a real `MicrophoneAudioWorklet` / `MicrophoneStream` layer — not the energy meter — would track.",
    "exports": [
      {
        "name": "checkInsufficientSpeech",
        "kind": "function",
        "signature": "checkInsufficientSpeech(frames)",
        "summary": ""
      },
      {
        "name": "checkLowSnr",
        "kind": "function",
        "signature": "checkLowSnr(frames)",
        "summary": ""
      },
      {
        "name": "checkClipping",
        "kind": "function",
        "signature": "checkClipping(frames)",
        "summary": ""
      },
      {
        "name": "checkEcho",
        "kind": "function",
        "signature": "checkEcho(echoResidualRatio)",
        "summary": "Echo contamination: strong correlation between the recording and a known TTS reference."
      },
      {
        "name": "checkMusic",
        "kind": "function",
        "signature": "checkMusic(frames)",
        "summary": "Music proxy: natural speech's frame-to-frame RMS is far less stable than sustained tonal/musical content."
      },
      {
        "name": "checkAnotherSpeaker",
        "kind": "function",
        "signature": "checkAnotherSpeaker(frames)",
        "summary": "Another-speaker proxy: an abrupt, sustained shift in low/high band energy balance partway through."
      },
      {
        "name": "checkGainDrift",
        "kind": "function",
        "signature": "checkGainDrift(frames)",
        "summary": "Gain drift: mean level of the last third vs first third of the speech should stay close."
      },
      {
        "name": "checkDeviceChanges",
        "kind": "function",
        "signature": "checkDeviceChanges(deviceChangeEventCount)",
        "summary": ""
      },
      {
        "name": "checkDroppedSamples",
        "kind": "function",
        "signature": "checkDroppedSamples(droppedSampleCount)",
        "summary": ""
      },
      {
        "name": "checkTooMuchSilence",
        "kind": "function",
        "signature": "checkTooMuchSilence(frames)",
        "summary": ""
      },
      {
        "name": "checkPoorCoverage",
        "kind": "function",
        "signature": "checkPoorCoverage(phraseCoverageRatio)",
        "summary": ""
      },
      {
        "name": "evaluateCaptureQuality",
        "kind": "function",
        "signature": "evaluateCaptureQuality(session)",
        "summary": "`session` = { frames, echoResidualRatio, deviceChangeEventCount, droppedSampleCount, phraseCoverageRatio }."
      },
      {
        "name": "CAPTURE_GATE_REASONS",
        "kind": "constant",
        "signature": "CAPTURE_GATE_REASONS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/CaptureGateProbe",
    "path": "agi/reference/particle_voice/risk/CaptureGateProbe.md",
    "source": "agi/particle_voice/risk/CaptureGateProbe.js",
    "import": "/agi/particle_voice/risk/CaptureGateProbe.js",
    "sourceHash": "b03045b72df9d73801add171378ba456834a7696c7c44a1eec4084f79fc2b151",
    "summary": "CaptureGateProbe.js — R14 Phase -1 risk spike orchestrator.",
    "exports": [
      {
        "name": "runCaptureGateProbe",
        "kind": "function",
        "signature": "async runCaptureGateProbe()",
        "summary": ""
      },
      {
        "name": "buildCaptureGateReceipt",
        "kind": "function",
        "signature": "async buildCaptureGateReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareCaptureGateReceiptDownload",
        "kind": "function",
        "signature": "prepareCaptureGateReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R14_CAPTURE_GATE_SCHEMA",
        "kind": "constant",
        "signature": "R14_CAPTURE_GATE_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/DeviceMatrixProbe",
    "path": "agi/reference/particle_voice/risk/DeviceMatrixProbe.md",
    "source": "agi/particle_voice/risk/DeviceMatrixProbe.js",
    "import": "/agi/particle_voice/risk/DeviceMatrixProbe.js",
    "sourceHash": "e268297bbe81287841f8f56f868f67537f54ce9cda6759f84147eff92d89369e",
    "summary": "DeviceMatrixProbe.js — R1 Phase -1 risk spike. \"device-matrix.html — record shader-f16, workgroup/storage/binding/invocation limits, AudioContext properties, crossOriginIsolated, AudioWorklet/ SharedArrayBuffer, OS VRAM quota.\" — particle-voice-26357c.md, Phase -1. Reuses the engine's own capability-probing primitives instead of reimplementing WebGPU adapter/device acquisition: - engine/core/gpu/GpuDevice.js  (GpuDevice.create, detectGpuPlatform) - engine/core/gpu/GpuInit.js    (isWebGpuSupported) - webgpu-os/kernel/VRAMTracker.js (default app/global quota) Read-only: the WebGPU device created here is destroyed immediately after limits/features are read, and the AudioContext is closed immediately after its properties are read.",
    "exports": [
      {
        "name": "probeWebGpu",
        "kind": "function",
        "signature": "async probeWebGpu()",
        "summary": "Acquire a throwaway GPUDevice via the engine's own GpuDevice, read limits/features, destroy it."
      },
      {
        "name": "probeAudioContext",
        "kind": "function",
        "signature": "async probeAudioContext()",
        "summary": "Construct a throwaway AudioContext, read its properties, close it."
      },
      {
        "name": "probeCrossOriginIsolation",
        "kind": "function",
        "signature": "probeCrossOriginIsolation()",
        "summary": ""
      },
      {
        "name": "probeSharedArrayBuffer",
        "kind": "function",
        "signature": "probeSharedArrayBuffer()",
        "summary": "Confirm SharedArrayBuffer is not just present but actually constructible."
      },
      {
        "name": "probeVramQuota",
        "kind": "function",
        "signature": "probeVramQuota()",
        "summary": "Read the OS default VRAM quota (webgpu-os/kernel/VRAMTracker.js), no kernel required."
      },
      {
        "name": "runDeviceMatrixProbe",
        "kind": "function",
        "signature": "async runDeviceMatrixProbe()",
        "summary": "Run every device-matrix probe and roll up an overall status. `unsupported` is a distinct outcome from `fail`, matching the plan's own Production Contract language (\"Unsupported hardware is reported explicitly as unsupported... the project does not degrade to an external runtime\"): - unsupported: WebGPU itself is unavailable on this device/browser. - fail: WebGPU is available, but a hard architectural requirement (crossOriginIsolated, SharedArrayBuffer, AudioWorklet) is missing — almost always a *serving* problem (missing COOP/COEP headers), not a hardware ceiling. Re-check via start_server.py before treating as real. - pass: WebGPU + AudioContext + cross-origin isolation + SharedArrayBuffer + AudioWorklet are all present."
      },
      {
        "name": "buildDeviceMatrixReceipt",
        "kind": "function",
        "signature": "async buildDeviceMatrixReceipt(result)",
        "summary": "Wrap a raw probe result into the immutable JSON receipt described by the R1 exit gate: \"Per-device limits + capability table.\""
      },
      {
        "name": "prepareDeviceMatrixReceiptDownload",
        "kind": "function",
        "signature": "prepareDeviceMatrixReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R1_DEVICE_MATRIX_SCHEMA",
        "kind": "constant",
        "signature": "R1_DEVICE_MATRIX_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EchoBargeIn",
    "path": "agi/reference/particle_voice/risk/EchoBargeIn.md",
    "source": "agi/particle_voice/risk/EchoBargeIn.js",
    "import": "/agi/particle_voice/risk/EchoBargeIn.js",
    "sourceHash": "211ae8237cd22101111d83b9733d5269a21814c04db1bb49493594748be9025e",
    "summary": "EchoBargeIn.js — R12 Phase -1 risk spike. \"echo reference ring, delay estimation, acoustic echo suppression, double-talk detection, no self-interruption.\" — Phase -1. Simplified fixed-delay echo cancellation (cross-correlation delay estimate + least-squares gain fit + direct subtraction), not an adaptive filter (NLMS/Kalman) — enough to prove the architecture correctly distinguishes pure echo from genuine double-talk. `AcousticEchoController.js` (Phase 2+) would use a real adaptive filter for real acoustic paths that drift over time; this fixed-delay model is a feasibility check.",
    "exports": [
      {
        "name": "createEchoReferenceRing",
        "kind": "function",
        "signature": "createEchoReferenceRing(capacitySamples)",
        "summary": ""
      },
      {
        "name": "estimateDelay",
        "kind": "function",
        "signature": "estimateDelay(reference, microphone, maxDelaySamples)",
        "summary": "Cross-correlation delay estimate: which shift of `reference` best predicts `microphone`."
      },
      {
        "name": "estimateGain",
        "kind": "function",
        "signature": "estimateGain(referenceAligned, microphone)",
        "summary": "Least-squares gain fit: minimizes ||microphone - gain*referenceAligned||^2."
      },
      {
        "name": "cancelEcho",
        "kind": "function",
        "signature": "cancelEcho(microphone, reference, delay, gain)",
        "summary": ""
      },
      {
        "name": "detectDoubleTalk",
        "kind": "function",
        "signature": "detectDoubleTalk(residual, referenceLevel, thresholdRatio = 0.3)",
        "summary": ""
      },
      {
        "name": "createBargeInController",
        "kind": "function",
        "signature": "createBargeInController({ minSustainedFrames = 3 } = {})",
        "summary": "Commit bargeInConfirmed only when double-talk is detected AND sustained for `minSustainedFrames`."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EchoBargeInProbe",
    "path": "agi/reference/particle_voice/risk/EchoBargeInProbe.md",
    "source": "agi/particle_voice/risk/EchoBargeInProbe.js",
    "import": "/agi/particle_voice/risk/EchoBargeInProbe.js",
    "sourceHash": "72323014c2a785bb1f400fb706a023e439e73d6dc8e50b9ba6d275d2047f3388",
    "summary": "EchoBargeInProbe.js — R12 Phase -1 risk spike orchestrator.",
    "exports": [
      {
        "name": "runEchoBargeInProbe",
        "kind": "function",
        "signature": "async runEchoBargeInProbe()",
        "summary": ""
      },
      {
        "name": "buildEchoBargeInReceipt",
        "kind": "function",
        "signature": "async buildEchoBargeInReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareEchoBargeInReceiptDownload",
        "kind": "function",
        "signature": "prepareEchoBargeInReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R12_ECHO_BARGE_IN_SCHEMA",
        "kind": "constant",
        "signature": "R12_ECHO_BARGE_IN_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EndpointController",
    "path": "agi/reference/particle_voice/risk/EndpointController.md",
    "source": "agi/particle_voice/risk/EndpointController.js",
    "import": "/agi/particle_voice/risk/EndpointController.js",
    "sourceHash": "a2aef1d2ffeef9497bfae45565791554370edc51afe9a09b0613e99e48fa328e",
    "summary": "EndpointController.js — R13 Phase -1 risk spike. \"min speech/silence durations, max-segment cap, causal commit bits (no lookahead).\" — Phase -1. A plain frame-counting debounce state machine on top of a boolean VAD decision (e.g. R11's `hysteresisState`) — every `update()` call only consumes the CURRENT frame's VAD decision plus its own prior internal state, never anything from the future, by construction (no buffering of upcoming frames anywhere in this module).",
    "exports": [
      {
        "name": "createEndpointController",
        "kind": "function",
        "signature": "createEndpointController({ frameMs, minSpeechMs = 200, minSilenceMs = 300, maxSegmentMs = 8000 })",
        "summary": "EndpointController.js — R13 Phase -1 risk spike. \"min speech/silence durations, max-segment cap, causal commit bits (no lookahead).\" — Phase -1. A plain frame-counting debounce state machine on top of a boolean VAD decision (e.g. R11's `hysteresisState`) — every `update()` call only consumes the CURRENT frame's VAD decision plus its own prior internal state, never anything from the future, by construction (no buffering of upcoming frames anywhere in this module)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EndpointLatencyProbe",
    "path": "agi/reference/particle_voice/risk/EndpointLatencyProbe.md",
    "source": "agi/particle_voice/risk/EndpointLatencyProbe.js",
    "import": "/agi/particle_voice/risk/EndpointLatencyProbe.js",
    "sourceHash": "e409bae8e8d4d9edd0d4674640e18338a59d7cc729f750d8841312a69a082561",
    "summary": "EndpointLatencyProbe.js — R13 Phase -1 risk spike orchestrator.",
    "exports": [
      {
        "name": "runEndpointLatencyProbe",
        "kind": "function",
        "signature": "async runEndpointLatencyProbe()",
        "summary": ""
      },
      {
        "name": "buildEndpointLatencyReceipt",
        "kind": "function",
        "signature": "async buildEndpointLatencyReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareEndpointLatencyReceiptDownload",
        "kind": "function",
        "signature": "prepareEndpointLatencyReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R13_ENDPOINT_LATENCY_SCHEMA",
        "kind": "constant",
        "signature": "R13_ENDPOINT_LATENCY_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EnergyDomains",
    "path": "agi/reference/particle_voice/risk/EnergyDomains.md",
    "source": "agi/particle_voice/risk/EnergyDomains.js",
    "import": "/agi/particle_voice/risk/EnergyDomains.js",
    "sourceHash": "206d716aeaa068d8688166ee3e9cf2436f2b4c0324c3dae4ccfa16e409c29ef3",
    "summary": "EnergyDomains.js — R15 Phase -1 risk spike. \"ObservedInputEnergy, GeneratedPhysiologyEnergy, and RenderedOutputEnergy are correctly separated; mic energy is labelled an uncertain acoustic-equivalent proxy, not lung pressure.\" — Phase -1. Three distinct, runtime-tagged value types plus an `assertDomain` guard that THROWS on cross-domain misuse — the concrete mechanism proving the architecture cannot silently conflate \"what the mic heard\" with \"what the physiology model generated\" or \"what the renderer produced\".",
    "exports": [
      {
        "name": "ObservedInputEnergy",
        "kind": "class",
        "signature": "class ObservedInputEnergy",
        "summary": "Energy derived from the real microphone signal. This is explicitly an uncertain ACOUSTIC-EQUIVALENT proxy — it correlates with vocal effort but is NOT a measurement of lung pressure, glottal state, or any other physiological quantity (room acoustics, mic gain/placement, and background noise all corrupt the mapping)."
      },
      {
        "name": "GeneratedPhysiologyEnergy",
        "kind": "class",
        "signature": "class GeneratedPhysiologyEnergy",
        "summary": "Energy/state produced by the neural physiology model (R6A NeuralPhysiologyState/BreathReservoir) driving the Voice Box."
      },
      {
        "name": "RenderedOutputEnergy",
        "kind": "class",
        "signature": "class RenderedOutputEnergy",
        "summary": "Energy of the final rendered PCM output (post Voice Box waveguide render)."
      },
      {
        "name": "assertDomain",
        "kind": "function",
        "signature": "assertDomain(energyObject, expectedDomain, context)",
        "summary": "Throws unless `energyObject.domain === expectedDomain` — the runtime separation guard."
      },
      {
        "name": "driveVoiceBox",
        "kind": "function",
        "signature": "driveVoiceBox(energyObject)",
        "summary": "Stand-in consumer: drives the Voice Box — must only ever accept generated-physiology energy."
      },
      {
        "name": "computeBargeInEvidence",
        "kind": "function",
        "signature": "computeBargeInEvidence(energyObject)",
        "summary": "Stand-in consumer: barge-in/VAD evidence — must only ever accept observed-input energy."
      },
      {
        "name": "computeLoudnessDisplay",
        "kind": "function",
        "signature": "computeLoudnessDisplay(energyObject)",
        "summary": "Stand-in consumer: UI loudness meter for what actually came out of the speakers — must only ever accept rendered-output energy."
      },
      {
        "name": "ENERGY_DOMAIN",
        "kind": "constant",
        "signature": "ENERGY_DOMAIN",
        "summary": "EnergyDomains.js — R15 Phase -1 risk spike. \"ObservedInputEnergy, GeneratedPhysiologyEnergy, and RenderedOutputEnergy are correctly separated; mic energy is labelled an uncertain acoustic-equivalent proxy, not lung pressure.\" — Phase -1. Three distinct, runtime-tagged value types plus an `assertDomain` guard that THROWS on cross-domain misuse — the concrete mechanism proving the architecture cannot silently conflate \"what the mic heard\" with \"what the physiology model generated\" or \"what the renderer produced\"."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/EnergyMeterProbe",
    "path": "agi/reference/particle_voice/risk/EnergyMeterProbe.md",
    "source": "agi/particle_voice/risk/EnergyMeterProbe.js",
    "import": "/agi/particle_voice/risk/EnergyMeterProbe.js",
    "sourceHash": "d89a83b81983dd55d12bb805a544754bd292c4bb8c21db04c546d2e6b09200a6",
    "summary": "EnergyMeterProbe.js — R10 Phase -1 risk spike orchestrator. No dedicated filename exists for R10 in the plan's risk/ page list either (named \"mic-energy-meter.html\" in the risk-page description text, used here directly).",
    "exports": [
      {
        "name": "runEnergyMeterProbe",
        "kind": "function",
        "signature": "async runEnergyMeterProbe()",
        "summary": ""
      },
      {
        "name": "buildEnergyMeterReceipt",
        "kind": "function",
        "signature": "async buildEnergyMeterReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareEnergyMeterReceiptDownload",
        "kind": "function",
        "signature": "prepareEnergyMeterReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R10_ENERGY_METER_SCHEMA",
        "kind": "constant",
        "signature": "R10_ENERGY_METER_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/Fft1024Kernel",
    "path": "agi/reference/particle_voice/risk/Fft1024Kernel.md",
    "source": "agi/particle_voice/risk/Fft1024Kernel.js",
    "import": "/agi/particle_voice/risk/Fft1024Kernel.js",
    "sourceHash": "d8385c80c2b0d88c88054730ca0d8dc20dc53434321f2712ce1af4a997c9e740",
    "summary": "Fft1024Kernel.js — R3 Phase -1 risk spike WGSL generator. No raw `.wgsl` files (Engine Alignment invariant #1): exports a `shader(params)` function, matching the eventual `nn/kernels/fft/fft1024_f32.js` / `ifft1024_f32.js` production kernels. Single-workgroup, in-workgroup-memory, iterative radix-2 Cooley-Tukey FFT. One workgroup (512 threads) computes exactly one 1024-point complex FFT; `workgroup_id.x` selects which frame of a batch to process, so N frames can be transformed in a single dispatch of N workgroups (needed to batch many overlapping STFT frames for the WOLA test). Direction is a per-dispatch uniform, not two separate shaders: forward uses the standard e^{-i2*pi*k/N} twiddle; inverse conjugates the input, runs the same forward butterfly network, then conjugates + scales the output by 1/N (IFFT(X) = (1/N) * conj(FFT(conj(X)))). Buffer layout (flat, frame-major): inputReal/inputImag/outputReal/outputImag are each `numFrames * 1024` f32 values; frame f occupies [f*1024, f*1024+1024).",
    "exports": [
      {
        "name": "fft1024Shader",
        "kind": "function",
        "signature": "fft1024Shader()",
        "summary": ""
      },
      {
        "name": "FFT_N",
        "kind": "constant",
        "signature": "FFT_N",
        "summary": "Fft1024Kernel.js — R3 Phase -1 risk spike WGSL generator. No raw `.wgsl` files (Engine Alignment invariant #1): exports a `shader(params)` function, matching the eventual `nn/kernels/fft/fft1024_f32.js` / `ifft1024_f32.js` production kernels. Single-workgroup, in-workgroup-memory, iterative radix-2 Cooley-Tukey FFT. One workgroup (512 threads) computes exactly one 1024-point complex FFT; `workgroup_id.x` selects which frame of a batch to process, so N frames can be transformed in a single dispatch of N workgroups (needed to batch many overlapping STFT frames for the WOLA test). Direction is a per-dispatch uniform, not two separate shaders: forward uses the standard e^{-i2*pi*k/N} twiddle; inverse conjugates the input, runs the same forward butterfly network, then conjugates + scales the output by 1/N (IFFT(X) = (1/N) * conj(FFT(conj(X)))). Buffer layout (flat, frame-major): inputReal/inputImag/outputReal/outputImag are each `numFrames * 1024` f32 values; frame f occupies [f*1024, f*1024+1024)."
      },
      {
        "name": "FFT_LOG2N",
        "kind": "constant",
        "signature": "FFT_LOG2N",
        "summary": ""
      },
      {
        "name": "FFT_ENTRY_POINT",
        "kind": "constant",
        "signature": "FFT_ENTRY_POINT",
        "summary": ""
      },
      {
        "name": "FFT_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "FFT_WORKGROUP_SIZE",
        "summary": ""
      },
      {
        "name": "FFT_DIRECTION_FORWARD",
        "kind": "constant",
        "signature": "FFT_DIRECTION_FORWARD",
        "summary": ""
      },
      {
        "name": "FFT_DIRECTION_INVERSE",
        "kind": "constant",
        "signature": "FFT_DIRECTION_INVERSE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/GlottalSource",
    "path": "agi/reference/particle_voice/risk/GlottalSource.md",
    "source": "agi/particle_voice/risk/GlottalSource.js",
    "import": "/agi/particle_voice/risk/GlottalSource.js",
    "sourceHash": "3d797545905407c2092f248ef593d98e8be41b44b8b07a4647d54a90c4c5efcb",
    "summary": "GlottalSource.js — R6 Phase -1 risk spike. A simplified (\"LF-lite\") glottal flow source, not the full Liljencrants- Fant model (which requires an iterative numerical solve of its alpha/wg/ ta/b parameters from open/speed quotient — out of scope for a feasibility spike). Each glottal pulse is a raised-cosine bump over the open phase of the cycle: flow(n) = 0.5 * (1 - cos(2*pi*n/Topen))   for 0 <= n < Topen flow(n) = 0                                for Topen <= n < T This is deliberately C1-continuous and zero at both pulse boundaries — clicks are a discontinuity in the waveform or its derivative, and this shape has neither, by construction, regardless of how F0 (period T) changes from one pulse to the next. `articulatory/glottal_lf.js` (Phase 2) is the real LF implementation this stands in for. Aspiration noise is mixed in proportional to instantaneous flow (breathy voicing), driven by a counter-based hash so it is chunk-boundary independent (same technique as StreamingContinuitySource.js).",
    "exports": [
      {
        "name": "createGlottalSource",
        "kind": "function",
        "signature": "createGlottalSource({ sampleRate, f0AtSample, openQuotient = 0.6, aspirationLevel = 0.0, noiseSeed = 777 })",
        "summary": "Create a glottal source generator. `f0AtSample(n)` lets callers drive a pitch sweep (F0 varies per absolute sample index); `openQuotient` is the fraction of each pitch period the folds are open (typical modal voice ~0.5-0.7)."
      },
      {
        "name": "noiseAt",
        "kind": "re-export",
        "signature": "noiseAt",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/GpuReadbackRing",
    "path": "agi/reference/particle_voice/risk/GpuReadbackRing.md",
    "source": "agi/particle_voice/risk/GpuReadbackRing.js",
    "import": "/agi/particle_voice/risk/GpuReadbackRing.js",
    "sourceHash": "3f7b6547d5aa87aaad922b56317a296d6f381e8679306f6685e94257ba6d0f33",
    "summary": "GpuReadbackRing.js — R4 Phase -1 risk spike. \"readback-audio.html — 6-8 MAP_READ slots, per-buffer mapAsync(), no global queue.onSubmittedWorkDone() for normal readback.\" — Phase -1. A minimal round-robin ring of MAP_READ|COPY_DST staging buffers. Each slot is copied into and mapped independently; the only synchronization primitive used is that slot's own `mapAsync()` promise — there is no `device.queue.onSubmittedWorkDone()` call anywhere in this module. Backpressure (more than `slotCount` requests in flight) is the caller's responsibility: `acquireFreeSlotIndex()` throws if no slot is free, so a caller must await/release before requesting more than `slotCount` ahead. This is a disposable Phase -1 probe, not the production `streaming/GPUReadbackRing.js` — same technique, smaller surface.",
    "exports": [
      {
        "name": "createGpuReadbackRing",
        "kind": "function",
        "signature": "createGpuReadbackRing(device, slotByteSize, slotCount = 8)",
        "summary": "GpuReadbackRing.js — R4 Phase -1 risk spike. \"readback-audio.html — 6-8 MAP_READ slots, per-buffer mapAsync(), no global queue.onSubmittedWorkDone() for normal readback.\" — Phase -1. A minimal round-robin ring of MAP_READ|COPY_DST staging buffers. Each slot is copied into and mapped independently; the only synchronization primitive used is that slot's own `mapAsync()` promise — there is no `device.queue.onSubmittedWorkDone()` call anywhere in this module. Backpressure (more than `slotCount` requests in flight) is the caller's responsibility: `acquireFreeSlotIndex()` throws if no slot is free, so a caller must await/release before requesting more than `slotCount` ahead. This is a disposable Phase -1 probe, not the production `streaming/GPUReadbackRing.js` — same technique, smaller surface."
      },
      {
        "name": "runPipelinedReadbackBenchmark",
        "kind": "function",
        "signature": "async runPipelinedReadbackBenchmark(device, ring, sourceBuffer, byteSize, iterations, maxInFlight, makeFrame, updateBufferFn)",
        "summary": "Run a pipelined readback benchmark: keeps up to `maxInFlight` mapAsync() requests outstanding at once (never more, never a global wait), writing a fresh frame into `sourceBuffer` before each new request."
      },
      {
        "name": "percentile",
        "kind": "function",
        "signature": "percentile(sortedValues, p)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/HeavySceneKernel",
    "path": "agi/reference/particle_voice/risk/HeavySceneKernel.md",
    "source": "agi/particle_voice/risk/HeavySceneKernel.js",
    "import": "/agi/particle_voice/risk/HeavySceneKernel.js",
    "sourceHash": "86485a1c03599da0a83472d1b4a988408246a4d8312146de2dc8358a39d77107",
    "summary": "HeavySceneKernel.js — R9 Phase -1 risk spike. \"renderer-voice-stress.html — voice compute beside a representative heavy ParticleRealms scene.\" — Phase -1. A fully procedural instanced-triangle render (positions/colors derived from @builtin(instance_index) in the vertex shader, no vertex/instance buffers needed) standing in for \"a representative heavy scene\" — many draw-time vertices without needing real mesh/scene data for a disposable risk spike.",
    "exports": [
      {
        "name": "heavySceneShader",
        "kind": "function",
        "signature": "heavySceneShader()",
        "summary": ""
      },
      {
        "name": "HEAVY_SCENE_VERTEX_ENTRY",
        "kind": "constant",
        "signature": "HEAVY_SCENE_VERTEX_ENTRY",
        "summary": "HeavySceneKernel.js — R9 Phase -1 risk spike. \"renderer-voice-stress.html — voice compute beside a representative heavy ParticleRealms scene.\" — Phase -1. A fully procedural instanced-triangle render (positions/colors derived from @builtin(instance_index) in the vertex shader, no vertex/instance buffers needed) standing in for \"a representative heavy scene\" — many draw-time vertices without needing real mesh/scene data for a disposable risk spike."
      },
      {
        "name": "HEAVY_SCENE_FRAGMENT_ENTRY",
        "kind": "constant",
        "signature": "HEAVY_SCENE_FRAGMENT_ENTRY",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/IntegrationManifest",
    "path": "agi/reference/particle_voice/risk/IntegrationManifest.md",
    "source": "agi/particle_voice/risk/IntegrationManifest.js",
    "import": "/agi/particle_voice/risk/IntegrationManifest.js",
    "sourceHash": "cd58598576660846ac0d682383750b132f3eb7bfe0451a437b5738e934837c07",
    "summary": "IntegrationManifest.js — R0 Phase -1 risk spike data. Every named ParticleRealms module referenced by the ParticleVoice plan's \"Codebase Integration Points\" tables (particle-voice-26357c.md), with the named exports each entry is expected to provide. `integration-path-audit.html` dynamically imports each `path` and checks every name in `exports` is present on the resulting module namespace, producing a pass/fail/missing receipt. Paths are root-relative so they resolve the same regardless of which page imports this manifest (served from the repo root by start_server.py). Exports were verified directly against source (grep for `^export`), not assumed from the plan's prose \"What it provides\" column. Where the plan's description implied an export that does not actually exist at module scope, a `note` field records the discrepancy so the audit page can surface it even on entries that technically \"pass\" (file imports fine, but a *different* name provides the capability than the plan implied).",
    "exports": [
      {
        "name": "INTEGRATION_MANIFEST",
        "kind": "constant",
        "signature": "INTEGRATION_MANIFEST",
        "summary": "IntegrationManifest.js — R0 Phase -1 risk spike data. Every named ParticleRealms module referenced by the ParticleVoice plan's \"Codebase Integration Points\" tables (particle-voice-26357c.md), with the named exports each entry is expected to provide. `integration-path-audit.html` dynamically imports each `path` and checks every name in `exports` is present on the resulting module namespace, producing a pass/fail/missing receipt. Paths are root-relative so they resolve the same regardless of which page imports this manifest (served from the repo root by start_server.py). Exports were verified directly against source (grep for `^export`), not assumed from the plan's prose \"What it provides\" column. Where the plan's description implied an export that does not actually exist at module scope, a `note` field records the discrepancy so the audit page can surface it even on entries that technically \"pass\" (file imports fine, but a *different* name provides the capability than the plan implied)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/IntegrationPathAudit",
    "path": "agi/reference/particle_voice/risk/IntegrationPathAudit.md",
    "source": "agi/particle_voice/risk/IntegrationPathAudit.js",
    "import": "/agi/particle_voice/risk/IntegrationPathAudit.js",
    "sourceHash": "4703d3bae709246ef74517b4bb134c4402ce76338994faee0ab652c95e03960f",
    "summary": "IntegrationPathAudit.js — R0 Phase -1 risk spike runner. \"Dynamic import/export checks for every named ParticleRealms module\" — particle-voice-26357c.md, Phase -1 / R0 Integration exit receipt. For every entry in IntegrationManifest.js this dynamically imports the module and checks that each expected named export is present on the resulting namespace object. No network/file mutation; read-only probing.",
    "exports": [
      {
        "name": "runIntegrationPathAudit",
        "kind": "function",
        "signature": "async runIntegrationPathAudit({ manifest = INTEGRATION_MANIFEST, importer = (path)",
        "summary": "Run the audit against a manifest."
      },
      {
        "name": "buildIntegrationAuditReceipt",
        "kind": "function",
        "signature": "async buildIntegrationAuditReceipt(result)",
        "summary": "Wrap a raw audit result into the immutable JSON receipt described by the R0 exit gate: \"JSON file listing every named import, expected export, actual export, result pass/fail/missing.\""
      },
      {
        "name": "prepareIntegrationAuditReceiptDownload",
        "kind": "function",
        "signature": "prepareIntegrationAuditReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R0_AUDIT_SCHEMA",
        "kind": "constant",
        "signature": "R0_AUDIT_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/KellyLochbaumWaveguide",
    "path": "agi/reference/particle_voice/risk/KellyLochbaumWaveguide.md",
    "source": "agi/particle_voice/risk/KellyLochbaumWaveguide.js",
    "import": "/agi/particle_voice/risk/KellyLochbaumWaveguide.js",
    "sourceHash": "c3a21ba8e7ebcfdc7bef0f5f6e1f030344acc5adc1170a31aabb43f959c93796",
    "summary": "KellyLochbaumWaveguide.js — R6 Phase -1 risk spike. Single-delay-per-section digital waveguide vocal tract model (Kelly & Lochbaum 1962 scattering junctions, as described in e.g. Julius O. Smith's \"Physical Audio Signal Processing\"). N cylindrical tube sections with cross-sectional areas A[0..N-1] give N-1 internal junction reflection coefficients k_i = (A_i - A_{i+1}) / (A_i + A_{i+1}). Boundary conditions: - Glottal end: source flow is injected with a partial reflection (`glottalReflection`) of the wave returning from the tract. - Lip end: an idealized open-end reflection (`lipReflection`, negative) plus a first-difference \"radiation\" filter approximating the differentiating characteristic of lip radiation. All boundary/junction coefficients are kept strictly inside (-1, 1), so the closed-loop lattice is passive/BIBO-stable by construction (no combination of area profile changes can cause unbounded energy growth). Nasal coupling and constriction noise are deliberate simplifications, not a rigorous multi-port (3-way) junction scattering derivation — see createNasalBranch() and step()'s constrictionIndex handling. Phase 2's `NasalTractJunction.js`/`ConstrictionNoise.js` need the rigorous version; this is enough to prove the overall architecture is stable and produces plausible, controllable behavior.",
    "exports": [
      {
        "name": "reflectionCoefficients",
        "kind": "function",
        "signature": "reflectionCoefficients(areas)",
        "summary": "KellyLochbaumWaveguide.js — R6 Phase -1 risk spike. Single-delay-per-section digital waveguide vocal tract model (Kelly & Lochbaum 1962 scattering junctions, as described in e.g. Julius O. Smith's \"Physical Audio Signal Processing\"). N cylindrical tube sections with cross-sectional areas A[0..N-1] give N-1 internal junction reflection coefficients k_i = (A_i - A_{i+1}) / (A_i + A_{i+1}). Boundary conditions: - Glottal end: source flow is injected with a partial reflection (`glottalReflection`) of the wave returning from the tract. - Lip end: an idealized open-end reflection (`lipReflection`, negative) plus a first-difference \"radiation\" filter approximating the differentiating characteristic of lip radiation. All boundary/junction coefficients are kept strictly inside (-1, 1), so the closed-loop lattice is passive/BIBO-stable by construction (no combination of area profile changes can cause unbounded energy growth). Nasal coupling and constriction noise are deliberate simplifications, not a rigorous multi-port (3-way) junction scattering derivation — see createNasalBranch() and step()'s constrictionIndex handling. Phase 2's `NasalTractJunction.js`/`ConstrictionNoise.js` need the rigorous version; this is enough to prove the overall architecture is stable and produces plausible, controllable behavior."
      },
      {
        "name": "uniformAreaProfile",
        "kind": "function",
        "signature": "uniformAreaProfile(numSections, area)",
        "summary": ""
      },
      {
        "name": "shapedAreaProfile",
        "kind": "function",
        "signature": "shapedAreaProfile(numSections, controlPoints)",
        "summary": "Linearly interpolated area profile through named (position, area) control points, position in [0, 1]."
      },
      {
        "name": "createVocalTract",
        "kind": "function",
        "signature": "createVocalTract({ numSections, areas, glottalReflection = 0.85, lipReflection = -0.9 })",
        "summary": ""
      },
      {
        "name": "createNasalBranch",
        "kind": "function",
        "signature": "createNasalBranch({ numSections, areas, nostrilReflection = -0.9 })",
        "summary": "Simplified nasal branch: its own short KL chain, driven by a coupled fraction of an oral-tract tap signal."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/MatmulBenchProbe",
    "path": "agi/reference/particle_voice/risk/MatmulBenchProbe.md",
    "source": "agi/particle_voice/risk/MatmulBenchProbe.js",
    "import": "/agi/particle_voice/risk/MatmulBenchProbe.js",
    "sourceHash": "919734368b13c240185108e99c106e9b528a57204202d296b38b8f19cae4e406",
    "summary": "MatmulBenchProbe.js — R2 Phase -1 risk spike. \"matmul-bench.html — W8A16 packed INT8 x FP16 matmul, FP32 accumulation, FP16 output; representative TTS shapes; full-model cost projection with >=30% headroom.\" — particle-voice-26357c.md, Phase -1. Shapes are PROVISIONAL: Phase 0 (spec freeze) has not happened yet, so the plan does not fix concrete model dimensions. These are derived from C:\\Coding\\PS2\\configs\\r0.yaml's own \"architecture smoke test\" convention (d_model=256, d_ff=4x d_model, n_layers=6) — the same repo's precedent for sizing a first small model before a full spec exists — combined with this plan's own VoiceChunker steady-state chunk bucket (64 frames) and stated mel-bin candidate (128). Revisit once spec/DeviceContract-v0.md exists. Reuses engine primitives instead of reimplementing device/buffer/pipeline plumbing: GpuDevice (engine/core/gpu/GpuDevice.js), GpuBuffer helpers (engine/core/gpu/GpuBuffer.js), and createComputePipelineChecked (agi/llm/kernels/PipelineUtils.js).",
    "exports": [
      {
        "name": "runMatmulBenchProbe",
        "kind": "function",
        "signature": "async runMatmulBenchProbe()",
        "summary": ""
      },
      {
        "name": "buildMatmulBenchReceipt",
        "kind": "function",
        "signature": "async buildMatmulBenchReceipt(result)",
        "summary": "Wrap a raw probe result into the R2 exit receipt."
      },
      {
        "name": "prepareMatmulBenchReceiptDownload",
        "kind": "function",
        "signature": "prepareMatmulBenchReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R2_MATMUL_BENCH_SCHEMA",
        "kind": "constant",
        "signature": "R2_MATMUL_BENCH_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/MatmulReferenceCpu",
    "path": "agi/reference/particle_voice/risk/MatmulReferenceCpu.md",
    "source": "agi/particle_voice/risk/MatmulReferenceCpu.js",
    "import": "/agi/particle_voice/risk/MatmulReferenceCpu.js",
    "sourceHash": "7a6851398ef3919b1c02cf5bb4d75c0edc36e040259214e3907b009cb6e025b6",
    "summary": "MatmulReferenceCpu.js — deterministic test-vector generation and a plain-JS scalar reference matmul for R2 (matmul-bench.html) parity checking. Mirrors the dequantization + accumulation order of MatmulW8A16Kernel.js's `main()` exactly (same k4 outer / b inner loop) so the only expected divergence between CPU (double) and GPU (f32) is floating-point rounding, not algorithmic difference.",
    "exports": [
      {
        "name": "randomInt8Weights",
        "kind": "function",
        "signature": "randomInt8Weights(K, N, seed = 1)",
        "summary": "Int8Array of length K*N, logical layout weights[n*K + k], values in [-127, 127]."
      },
      {
        "name": "randomActivations",
        "kind": "function",
        "signature": "randomActivations(M, K, seed = 2)",
        "summary": "Float32Array of length M*K, layout activations[m*K + k], values in [-1, 1]."
      },
      {
        "name": "randomScales",
        "kind": "function",
        "signature": "randomScales(N, seed = 3)",
        "summary": "Float32Array of length N — typical per-channel INT8 quantization scales."
      },
      {
        "name": "packWeightsInt8x4",
        "kind": "function",
        "signature": "packWeightsInt8x4(weightsInt8, K, N)",
        "summary": "Pack logical [K,N] int8 weights into the u32 layout MatmulW8A16Kernel.js expects."
      },
      {
        "name": "referenceMatmul",
        "kind": "function",
        "signature": "referenceMatmul(activations, weightsInt8, scales, M, K, N)",
        "summary": "Plain-JS reference matmul, same math/order as the WGSL kernel. Returns Float64Array[M*N]."
      },
      {
        "name": "compareResults",
        "kind": "function",
        "signature": "compareResults(gpuResult, referenceResult)",
        "summary": "Max-abs and max-relative error between a GPU result and the CPU reference."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/MatmulW8A16Kernel",
    "path": "agi/reference/particle_voice/risk/MatmulW8A16Kernel.md",
    "source": "agi/particle_voice/risk/MatmulW8A16Kernel.js",
    "import": "/agi/particle_voice/risk/MatmulW8A16Kernel.js",
    "sourceHash": "4722455b8d4b9a404fd972a26842a005b8fff3a90ad085da26365eb8e0579996",
    "summary": "MatmulW8A16Kernel.js — R2 Phase -1 risk spike WGSL generator. No raw `.wgsl` files (Engine Alignment invariant #1): this module exports a `shader(params)` function that returns a WGSL string, same convention as `agi/llm/kernels/shaders/*.js` and the eventual `nn/kernels/matmul/matmul_w8a16.js`. MVP scope for this spike: INT8 weights (4-packed per u32, per-output-channel scale) x FP32 activations, FP32 accumulation, FP32 output. This is a deliberately smaller slice of the plan's stated \"W8A16 packed INT8 x FP16 matmul, FP32 accumulation, FP16 output\" — FP16 activation/output packing needs its own bit-packing design (2xf16 per u32, gated on `shader-f16` from R1) and is left as an explicit follow-up rather than an unverified claim. Weight layout expected by this kernel: - Logical weights are [K, N] (K = input/hidden dim, N = output dim). - Packed as `packedWeights[n * (K/4) + k4]`, a u32 whose 4 bytes are the int8 weights for k = k4*4 + 0..3 (little end = lowest k). - `scales[n]` is the per-output-channel dequantization scale. - K must be a multiple of 4.",
    "exports": [
      {
        "name": "matmulW8a16Shader",
        "kind": "function",
        "signature": "matmulW8a16Shader()",
        "summary": ""
      },
      {
        "name": "MATMUL_W8A16_ENTRY_POINT",
        "kind": "constant",
        "signature": "MATMUL_W8A16_ENTRY_POINT",
        "summary": "MatmulW8A16Kernel.js — R2 Phase -1 risk spike WGSL generator. No raw `.wgsl` files (Engine Alignment invariant #1): this module exports a `shader(params)` function that returns a WGSL string, same convention as `agi/llm/kernels/shaders/*.js` and the eventual `nn/kernels/matmul/matmul_w8a16.js`. MVP scope for this spike: INT8 weights (4-packed per u32, per-output-channel scale) x FP32 activations, FP32 accumulation, FP32 output. This is a deliberately smaller slice of the plan's stated \"W8A16 packed INT8 x FP16 matmul, FP32 accumulation, FP16 output\" — FP16 activation/output packing needs its own bit-packing design (2xf16 per u32, gated on `shader-f16` from R1) and is left as an explicit follow-up rather than an unverified claim. Weight layout expected by this kernel: - Logical weights are [K, N] (K = input/hidden dim, N = output dim). - Packed as `packedWeights[n * (K/4) + k4]`, a u32 whose 4 bytes are the int8 weights for k = k4*4 + 0..3 (little end = lowest k). - `scales[n]` is the per-output-channel dequantization scale. - K must be a multiple of 4."
      },
      {
        "name": "MATMUL_W8A16_WORKGROUP_SIZE",
        "kind": "constant",
        "signature": "MATMUL_W8A16_WORKGROUP_SIZE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/NeuralPhysiologyState",
    "path": "agi/reference/particle_voice/risk/NeuralPhysiologyState.md",
    "source": "agi/particle_voice/risk/NeuralPhysiologyState.js",
    "import": "/agi/particle_voice/risk/NeuralPhysiologyState.js",
    "sourceHash": "d4942e133aebf22e0c700ba6d5f6a12191915ee7ef0b2e8a46d787e3fee3fcac",
    "summary": "NeuralPhysiologyState.js — R6A Phase -1 risk spike. \"NeuralPhysiologyState schema: RespiratoryDriveHead, GlottalStateHead, TractGestureHead, ArticulationEventHead, UncertaintyHead version 0x00010000.\" — particle-voice-26357c.md, Phase 2 (adopted early here for the R6A wiring receipt). No trained `ArticulationHead` model exists yet (that's Phase 3+). This provides the state SCHEMA plus a deterministic scripted stand-in (\"fake head\") that produces plausible per-frame physiology values as a pure function of time and an `intervention` parameter set — enough to prove the wiring ArticulationHead -> NeuralPhysiologyState -> BreathReservoir/ParticleTract -> renderer actually routes control values through to the final PCM, which is what R6A's exit gate asks for.",
    "exports": [
      {
        "name": "createNeuralPhysiologyState",
        "kind": "function",
        "signature": "createNeuralPhysiologyState({ timestampSeconds, respiratoryDrive, glottalState, tractGesture, articulationEvent, uncertainty, })",
        "summary": ""
      },
      {
        "name": "createScriptedArticulationHead",
        "kind": "function",
        "signature": "createScriptedArticulationHead(intervention = {})",
        "summary": "Deterministic scripted stand-in for a trained ArticulationHead: a short \"utterance\" that opens into a steady vowel then applies a brief closure gesture, with `intervention.respiratoryScale` and `intervention.constrictionOpennessFloor` letting a caller apply a physiology intervention and compare against a baseline run."
      },
      {
        "name": "NEURAL_PHYSIOLOGY_STATE_VERSION",
        "kind": "constant",
        "signature": "NEURAL_PHYSIOLOGY_STATE_VERSION",
        "summary": "NeuralPhysiologyState.js — R6A Phase -1 risk spike. \"NeuralPhysiologyState schema: RespiratoryDriveHead, GlottalStateHead, TractGestureHead, ArticulationEventHead, UncertaintyHead version 0x00010000.\" — particle-voice-26357c.md, Phase 2 (adopted early here for the R6A wiring receipt). No trained `ArticulationHead` model exists yet (that's Phase 3+). This provides the state SCHEMA plus a deterministic scripted stand-in (\"fake head\") that produces plausible per-frame physiology values as a pure function of time and an `intervention` parameter set — enough to prove the wiring ArticulationHead -> NeuralPhysiologyState -> BreathReservoir/ParticleTract -> renderer actually routes control values through to the final PCM, which is what R6A's exit gate asks for."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/OsMediaPermissionProbe",
    "path": "agi/reference/particle_voice/risk/OsMediaPermissionProbe.md",
    "source": "agi/particle_voice/risk/OsMediaPermissionProbe.js",
    "import": "/agi/particle_voice/risk/OsMediaPermissionProbe.js",
    "sourceHash": "775b641f77d994d59d2cf38b6df546cc25a9044317ff469f874b483d3f960a5d",
    "summary": "OsMediaPermissionProbe.js — R16 Phase -1 risk spike. \"Secure context + user gesture + getUserMedia + enumerateDevices + AudioWorklet + SharedArrayBuffer all succeed through kernel.media.* / kernel.audio.* syscalls; media.audio / audio.play / microphone capabilities are correctly required and enforced by guardSyscalls; OsMediaBridge.js never touches raw navigator.mediaDevices.\" — Phase -1. `agi/particle_voice/activity/bridge/OsMediaBridge.js` does not exist yet (Phase -1 precedes Phase 1+ implementation) — that check is a forward contract placeholder here, not yet applicable. Everything else tests the REAL `webgpu-os/kernel/Syscalls.js` (`createSyscalls`, `guardSyscalls`) and `webgpu-os/kernel/Permissions.js` (`Permissions`) directly, not a stand-in — this spike is about the OS integration contract itself.",
    "exports": [
      {
        "name": "runOsMediaPermissionProbe",
        "kind": "function",
        "signature": "async runOsMediaPermissionProbe()",
        "summary": ""
      },
      {
        "name": "buildOsMediaPermissionReceipt",
        "kind": "function",
        "signature": "async buildOsMediaPermissionReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareOsMediaPermissionReceiptDownload",
        "kind": "function",
        "signature": "prepareOsMediaPermissionReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R16_OS_MEDIA_PERMISSION_SCHEMA",
        "kind": "constant",
        "signature": "R16_OS_MEDIA_PERMISSION_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/ParticleEnergyMeter",
    "path": "agi/reference/particle_voice/risk/ParticleEnergyMeter.md",
    "source": "agi/particle_voice/risk/ParticleEnergyMeter.js",
    "import": "/agi/particle_voice/risk/ParticleEnergyMeter.js",
    "sourceHash": "abf80a11fdc5019d8da1daed093b6d2a754ad786b47013015ded6fe4d781806d",
    "summary": "ParticleEnergyMeter.js — R10 Phase -1 risk spike. \"RMS, dBFS, peak, clipping, DC offset, crest factor, attack/release, SNR, low/mid/high band energy, spectral flux; adaptive noise floor while no speech/echo present.\" — Phase -1. Plain-JS CPU implementation (small analysis frames, not a GPU kernel — this is a metering/control-path computation, not a bulk DSP kernel like R3's FFT). Includes its own compact FFT for band energy/spectral flux rather than importing Fft1024Kernel.js, which generates a WGSL string for GPU dispatch, not a callable JS magnitude-spectrum function — a different form for a different execution context, same algorithm family as MatmulReferenceCpu.js is to matmul_w8a16.js.",
    "exports": [
      {
        "name": "createParticleEnergyMeter",
        "kind": "function",
        "signature": "createParticleEnergyMeter({ sampleRate, frameSize = 256, attackMs = 5, releaseMs = 60, noiseFloorRecoveryPerFrame = 0.0008 })",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/ParticleVad",
    "path": "agi/reference/particle_voice/risk/ParticleVad.md",
    "source": "agi/particle_voice/risk/ParticleVad.js",
    "import": "/agi/particle_voice/risk/ParticleVad.js",
    "sourceHash": "956bee93ed0dc39a6adc9930b4538632ba3b717918ceca6635cf1a9897cc2c3b",
    "summary": "ParticleVad.js — R11 Phase -1 risk spike. \"ParticleVAD-1 11-head output smoothness, hysteresis, no flicker; latency p50/p95 of speech-start/end decisions.\" — Phase -1. No trained ParticleVAD-1 model exists yet — this is a heuristic, energy/spectral-flux-driven stand-in (built on R10's ParticleEnergyMeter), not a neural VAD. The plan names \"11 continuous probability heads\" without enumerating all 11 in the sections read for this spike; the 11 names below are a reasonable, clearly-labeled interpretation, not a verified spec. Testing rigor here focuses on the primary `speechActive` head's smoothness/hysteresis/latency, since that is what a heuristic stand-in can meaningfully validate.",
    "exports": [
      {
        "name": "createParticleVad",
        "kind": "function",
        "signature": "createParticleVad({ sampleRate, frameSize = 256, smoothingMs = 30, riseThreshold = 0.55, fallThreshold = 0.35 })",
        "summary": ""
      },
      {
        "name": "VAD_HEAD_NAMES",
        "kind": "constant",
        "signature": "VAD_HEAD_NAMES",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/R4ReadbackAudioWorklet.worklet",
    "path": "agi/reference/particle_voice/risk/R4ReadbackAudioWorklet.worklet.md",
    "source": "agi/particle_voice/risk/R4ReadbackAudioWorklet.worklet.js",
    "import": "/agi/particle_voice/risk/R4ReadbackAudioWorklet.worklet.js",
    "sourceHash": "1d3b3439793159b2564ce26fdb0b656cd168ef2b673b3a445fd62c50479a4d49",
    "summary": "R4ReadbackAudioWorklet.worklet.js — R4 Phase -1 risk spike. \"allocation-free AudioWorklet.\" — Phase -1. Reference/mirror target: engine/audio/synth/PatchRunner.worklet.js (SAB parameter reads, no allocation in process()). Consumes a single-producer/single-consumer PCM ring living in a SharedArrayBuffer (written by the main thread, standing in for what the production path would fill via GPUReadbackRing.js + SharedPCMRing.js). `process()` performs zero allocation: no `new`, no object/array literals, no `await`, no `throw`, no `JSON` — only typed-array indexing and Atomics on pre-existing SharedArrayBuffer-backed views. Control layout (Int32Array, SharedArrayBuffer, 4 x int32): [0] writeIndex   — next PCM slot the producer will write (main thread) [1] readIndex    — next PCM slot this processor will read [2] underrunCount — incremented whenever fewer than one quantum of samples are available [3] processCallCount — incremented every process() call This file cannot be dynamically `import()`-ed from a normal page context: `AudioWorkletProcessor`/`registerProcessor` only exist inside the AudioWorkletGlobalScope. Load it via `audioContext.audioWorklet.addModule(url)`.",
    "exports": []
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/ReadbackAudioProbe",
    "path": "agi/reference/particle_voice/risk/ReadbackAudioProbe.md",
    "source": "agi/particle_voice/risk/ReadbackAudioProbe.js",
    "import": "/agi/particle_voice/risk/ReadbackAudioProbe.js",
    "sourceHash": "ee382595d2205c5bad45378893ad5a64b97d46942e6561188fb10a94e4032e4e",
    "summary": "ReadbackAudioProbe.js — R4 Phase -1 risk spike. Exit gate: \"Per-buffer mapAsync() p50/p95/p99; AudioWorklet survives variable quantum and rebuild; one-hour zero-underrun baseline.\" — Phase -1. Honest scoping note: a literal one-hour automated soak is not run by this probe (impractical inside a single interactive/CI session). This runs a short deterministic soak (SHORT_BASELINE_SOAK_MS) as a scaled-down proxy, clearly labeled as such in the receipt, plus an underrun-detector self-test (proves the zero-underrun count is meaningful, not tautological) and a full teardown+rebuild cycle. The full one-hour soak is left as an explicit manual/CI follow-up — see `oneHourSoak` in the result. \"Variable quantum\" is not directly controllable from stable browser APIs (render quantum size is fixed at 128 in all current stable implementations); this is verified as a static property of the processor (R4ReadbackAudioWorklet.worklet.js reads `channel.length` for its loop bound rather than assuming 128) plus dynamic survival across two independent AudioContext instances (which may differ in `sampleRate`).",
    "exports": [
      {
        "name": "createSharedRing",
        "kind": "function",
        "signature": "createSharedRing(sampleRate)",
        "summary": ""
      },
      {
        "name": "prefillRing",
        "kind": "function",
        "signature": "prefillRing(ring, sampleRate, prefillMs)",
        "summary": "Synchronously write `prefillMs` of audio into the ring before the worklet node is ever pulled. Absorbs the startup race between the interval-based producer's first tick (~PRODUCER_INTERVAL_MS away) and the AudioWorklet's first process() call, which can happen sooner — without this, a handful of transient underruns at cold start are expected, not a sustained-rate problem, but they'd still show up in a \"zero-underrun baseline\" reading."
      },
      {
        "name": "startProducer",
        "kind": "function",
        "signature": "startProducer(ring, sampleRate, initialSamplesProduced = 0)",
        "summary": "Wall-clock-paced producer: writes exactly enough samples each tick to match elapsed real time (minus whatever was already prefilled), so it neither underruns (falls behind) nor overflows the fixed-capacity ring (a fixed-chunk-per-tick producer would race arbitrarily far ahead of the consumer and wrap/corrupt the ring)."
      },
      {
        "name": "createWorkletSetup",
        "kind": "function",
        "signature": "async createWorkletSetup()",
        "summary": ""
      },
      {
        "name": "teardownWorkletSetup",
        "kind": "function",
        "signature": "async teardownWorkletSetup(setup)",
        "summary": ""
      },
      {
        "name": "runReadbackAudioProbe",
        "kind": "function",
        "signature": "async runReadbackAudioProbe()",
        "summary": ""
      },
      {
        "name": "buildReadbackAudioReceipt",
        "kind": "function",
        "signature": "async buildReadbackAudioReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareReadbackAudioReceiptDownload",
        "kind": "function",
        "signature": "prepareReadbackAudioReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "runLongSoak",
        "kind": "function",
        "signature": "async runLongSoak(durationMs)",
        "summary": "Exposed on window for a human/CI to opt into the real one-hour soak manually."
      },
      {
        "name": "R4_READBACK_AUDIO_SCHEMA",
        "kind": "constant",
        "signature": "R4_READBACK_AUDIO_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/ReceiptCrypto",
    "path": "agi/reference/particle_voice/risk/ReceiptCrypto.md",
    "source": "agi/particle_voice/risk/ReceiptCrypto.js",
    "import": "/agi/particle_voice/risk/ReceiptCrypto.js",
    "sourceHash": "6903c6afc41c12cb74b9ee40f4398a91d9816930f3f12257a7a3e8c68bfcfae2",
    "summary": "ReceiptCrypto.js — shared canonicalization + SHA-256 hashing for every Phase -1 risk-spike JSON receipt under `agi/particle_voice/risk/`. Kept tiny and dependency-free so any risk page can import it directly.",
    "exports": [
      {
        "name": "canonicalizeForHash",
        "kind": "function",
        "signature": "canonicalizeForHash(value)",
        "summary": "Recursively sort object keys so hashing is independent of insertion order."
      },
      {
        "name": "sha256HexOf",
        "kind": "function",
        "signature": "async sha256HexOf(value)",
        "summary": "SHA-256 hex digest of a canonicalized JSON value, or null if WebCrypto is unavailable."
      },
      {
        "name": "receiptHashOf",
        "kind": "function",
        "signature": "async receiptHashOf(value)",
        "summary": "Convenience: `sha256:<hex>` prefixed digest, or null."
      },
      {
        "name": "prepareJsonDownload",
        "kind": "function",
        "signature": "prepareJsonDownload(receipt, filenamePrefix)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/RendererVoiceStressProbe",
    "path": "agi/reference/particle_voice/risk/RendererVoiceStressProbe.md",
    "source": "agi/particle_voice/risk/RendererVoiceStressProbe.js",
    "import": "/agi/particle_voice/risk/RendererVoiceStressProbe.js",
    "sourceHash": "a2a49b5f9103ab70f40983bde0956d59882809c0f9a93003b14b11c9f328c83a",
    "summary": "RendererVoiceStressProbe.js — R9 Phase -1 risk spike. Exit gate: \"Voice + heavy scene runs without audio underruns or persistent render frame drops.\" — Phase -1. Runs a heavy procedural instanced-triangle render pass and a batched FFT \"voice compute\" dispatch every animation frame, concurrently with R4's real AudioWorklet + SharedArrayBuffer PCM ring (imported directly, not reimplemented), for a few seconds. Measures both render frame-time health and audio underrun count over the same window.",
    "exports": [
      {
        "name": "runRendererVoiceStressProbe",
        "kind": "function",
        "signature": "async runRendererVoiceStressProbe()",
        "summary": ""
      },
      {
        "name": "buildRendererVoiceStressReceipt",
        "kind": "function",
        "signature": "async buildRendererVoiceStressReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareRendererVoiceStressReceiptDownload",
        "kind": "function",
        "signature": "prepareRendererVoiceStressReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R9_RENDERER_VOICE_STRESS_SCHEMA",
        "kind": "constant",
        "signature": "R9_RENDERER_VOICE_STRESS_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/SignalMetrics",
    "path": "agi/reference/particle_voice/risk/SignalMetrics.md",
    "source": "agi/particle_voice/risk/SignalMetrics.js",
    "import": "/agi/particle_voice/risk/SignalMetrics.js",
    "sourceHash": "4e86e594094b72b81c2e6a8b4a7dacac0c0a0db830c343558c3dd661be5c57d2",
    "summary": "SignalMetrics.js — shared signal-quality helpers for Phase -1 risk pages (extracted out of VoiceBoxWaveguideProbe.js when a third consumer appeared, to avoid a third copy-paste of rms/peakAbs/hasNonFinite).",
    "exports": [
      {
        "name": "rms",
        "kind": "function",
        "signature": "rms(signal, start = 0, end = signal.length)",
        "summary": "SignalMetrics.js — shared signal-quality helpers for Phase -1 risk pages (extracted out of VoiceBoxWaveguideProbe.js when a third consumer appeared, to avoid a third copy-paste of rms/peakAbs/hasNonFinite)."
      },
      {
        "name": "peakAbs",
        "kind": "function",
        "signature": "peakAbs(signal, start = 0, end = signal.length)",
        "summary": ""
      },
      {
        "name": "hasNonFinite",
        "kind": "function",
        "signature": "hasNonFinite(signal)",
        "summary": ""
      },
      {
        "name": "maxAbsDiff",
        "kind": "function",
        "signature": "maxAbsDiff(a, b, startIndex = 0, endIndex = a.length)",
        "summary": ""
      },
      {
        "name": "detectClicks",
        "kind": "function",
        "signature": "detectClicks(signal, windowSize = 256, thresholdMultiplier = 12)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/StftWolaProbe",
    "path": "agi/reference/particle_voice/risk/StftWolaProbe.md",
    "source": "agi/particle_voice/risk/StftWolaProbe.js",
    "import": "/agi/particle_voice/risk/StftWolaProbe.js",
    "sourceHash": "9f75b40f63e418cbcf80db58e911d0939a429202201eedf2d681b4d9871b73f3",
    "summary": "StftWolaProbe.js — R3 Phase -1 risk spike. \"stft-wola.html — 1024-point f32 FFT/IFFT and normalized WOLA for 320-sample hop; identity, impulse, sweep, noise tests.\" — Phase -1. Exit gate: \"Identity tests pass; workgroup limits respected; normalized WOLA ripple below audible threshold.\" Identity tests run a single frame through forward FFT then inverse FFT (both on GPU) and compare to the original signal. The WOLA test runs many overlapping analysis-windowed frames through the same round trip, applies a synthesis window, overlap-adds, and divides by the exact sum-of-squares envelope — with exact normalization the round trip is mathematically identity, so any measured \"ripple\" is real FFT/window/OLA implementation error, not a windowing-shape artifact.",
    "exports": [
      {
        "name": "runStftWolaProbe",
        "kind": "function",
        "signature": "async runStftWolaProbe()",
        "summary": ""
      },
      {
        "name": "buildStftWolaReceipt",
        "kind": "function",
        "signature": "async buildStftWolaReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareStftWolaReceiptDownload",
        "kind": "function",
        "signature": "prepareStftWolaReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R3_STFT_WOLA_SCHEMA",
        "kind": "constant",
        "signature": "R3_STFT_WOLA_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/StreamingContinuityProbe",
    "path": "agi/reference/particle_voice/risk/StreamingContinuityProbe.md",
    "source": "agi/particle_voice/risk/StreamingContinuityProbe.js",
    "import": "/agi/particle_voice/risk/StreamingContinuityProbe.js",
    "sourceHash": "f4d6893db72a4b9c236d7bc60ff856ec2e611b7cebf0611c04be930eb947a099",
    "summary": "StreamingContinuityProbe.js — R5 Phase -1 risk spike orchestrator. Pure JS, no GPU/AudioContext dependency: R5's exit gate is an architectural/correctness property of the counter design (does chunking change the output?), not a GPU performance question, so no kernel is needed to validate it. See StreamingContinuitySource.js for the mechanism.",
    "exports": [
      {
        "name": "runStreamingContinuityProbe",
        "kind": "function",
        "signature": "async runStreamingContinuityProbe()",
        "summary": ""
      },
      {
        "name": "buildStreamingContinuityReceipt",
        "kind": "function",
        "signature": "async buildStreamingContinuityReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareStreamingContinuityReceiptDownload",
        "kind": "function",
        "signature": "prepareStreamingContinuityReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R5_STREAMING_CONTINUITY_SCHEMA",
        "kind": "constant",
        "signature": "R5_STREAMING_CONTINUITY_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/StreamingContinuitySource",
    "path": "agi/reference/particle_voice/risk/StreamingContinuitySource.md",
    "source": "agi/particle_voice/risk/StreamingContinuitySource.js",
    "import": "/agi/particle_voice/risk/StreamingContinuitySource.js",
    "sourceHash": "341d03da1e1b0ffd7eeb86ff7ca668715da617ee4885e39e3ace0cef2d8e1f26",
    "summary": "StreamingContinuitySource.js — R5 Phase -1 risk spike. Exit gate: \"Phase-continuous harmonic/noise across arbitrary chunk boundaries; absolute-counter noise chunk-independent.\" — Phase -1. There is no dedicated filename for R5 in the plan's risk/ page list (it names R0-R4, R6-R16 pages explicitly but not R5) — this file/page covers that exit gate directly. The technique: every sample's value is a pure function of its ABSOLUTE sample index (harmonic phase = 2*pi*f0*n/sampleRate; noise = a counter-based hash of n), with no persistent oscillator/PRNG state threaded between chunks and no chunk-relative counter anywhere. Chunking therefore cannot affect the output — not \"close enough\", bit-identical — because the per-sample computation never reads chunk position at all. This is the mechanism `harmonic_source.js`/`noise_source.js` (Phase 2) must use for random chunk-boundary training and streaming to work.",
    "exports": [
      {
        "name": "noiseAt",
        "kind": "function",
        "signature": "noiseAt(absoluteIndex, seed)",
        "summary": "Counter-based hash PRNG: deterministic pseudo-random float in [-1, 1], pure function of (index, seed)."
      },
      {
        "name": "harmonicAt",
        "kind": "function",
        "signature": "harmonicAt(absoluteIndex, sampleRate, f0Hz)",
        "summary": "Pure function of absolute sample index — no carried oscillator phase state."
      },
      {
        "name": "synthesizeAbsoluteCounter",
        "kind": "function",
        "signature": "synthesizeAbsoluteCounter(totalSamples, sampleRate, f0Hz, noiseSeed, chunkSizes)",
        "summary": "Correct (absolute-counter) synthesis: chunking `chunkSizes` (cycled) has zero effect on the output because each sample only ever reads its absolute index `n`, never a position relative to the current chunk."
      },
      {
        "name": "synthesizeChunkRelativeCounterBuggy",
        "kind": "function",
        "signature": "synthesizeChunkRelativeCounterBuggy(totalSamples, sampleRate, f0Hz, noiseSeed, chunkSizes)",
        "summary": "Deliberately buggy (chunk-relative-counter) synthesis for the self-test: resets its local counter to 0 at the start of every chunk, so output DOES depend on how the signal was chunked — used only to prove the bit-exact comparison in this probe actually catches phase discontinuity."
      },
      {
        "name": "maxAbsDiff",
        "kind": "function",
        "signature": "maxAbsDiff(a, b)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/TinyAutodiff",
    "path": "agi/reference/particle_voice/risk/TinyAutodiff.md",
    "source": "agi/particle_voice/risk/TinyAutodiff.js",
    "import": "/agi/particle_voice/risk/TinyAutodiff.js",
    "sourceHash": "ec5c004232925a8f0fcac03bb0685a7bfdbdf568132330c3640e25bfed94b399",
    "summary": "TinyAutodiff.js — R7 Phase -1 risk spike. Hand-wired forward+backward layers (Linear, Conv1D, ReLU, Sigmoid), losses (MSE, BCE), and an AdamW optimizer — deliberately NOT a general computational-graph autodiff engine. `ParticleTape`/`BackwardGraphCompiler`/ `GradientGraph` (Phase 1, nn/autodiff/) are the real general system; R7's job is to prove models at this parameter scale can be forward+backward trained in-browser at all, not to build that general architecture early. All arrays are plain Float32Array with row/channel-major layouts documented per function; batch=1 throughout (a single fixed sequence/vector per step) is sufficient for R7's memorization-convergence test.",
    "exports": [
      {
        "name": "makeRng",
        "kind": "function",
        "signature": "makeRng(seed)",
        "summary": "TinyAutodiff.js — R7 Phase -1 risk spike. Hand-wired forward+backward layers (Linear, Conv1D, ReLU, Sigmoid), losses (MSE, BCE), and an AdamW optimizer — deliberately NOT a general computational-graph autodiff engine. `ParticleTape`/`BackwardGraphCompiler`/ `GradientGraph` (Phase 1, nn/autodiff/) are the real general system; R7's job is to prove models at this parameter scale can be forward+backward trained in-browser at all, not to build that general architecture early. All arrays are plain Float32Array with row/channel-major layouts documented per function; batch=1 throughout (a single fixed sequence/vector per step) is sufficient for R7's memorization-convergence test."
      },
      {
        "name": "createLinear",
        "kind": "function",
        "signature": "createLinear(inputDim, outputDim, rng)",
        "summary": ""
      },
      {
        "name": "createReLU",
        "kind": "function",
        "signature": "createReLU()",
        "summary": ""
      },
      {
        "name": "createSigmoid",
        "kind": "function",
        "signature": "createSigmoid()",
        "summary": ""
      },
      {
        "name": "createConv1D",
        "kind": "function",
        "signature": "createConv1D(inChannels, outChannels, kernelSize, rng)",
        "summary": ""
      },
      {
        "name": "mseLoss",
        "kind": "function",
        "signature": "mseLoss(pred, target)",
        "summary": ""
      },
      {
        "name": "bceLoss",
        "kind": "function",
        "signature": "bceLoss(pred, target)",
        "summary": ""
      },
      {
        "name": "clipGradGlobalNorm",
        "kind": "function",
        "signature": "clipGradGlobalNorm(gradArrays, maxNorm)",
        "summary": "Clip the global L2 norm across all gradient arrays in place — cheap insurance against a deep stack diverging."
      },
      {
        "name": "createAdamW",
        "kind": "function",
        "signature": "createAdamW(paramArrays, { lr = 0.01, beta1 = 0.9, beta2 = 0.999, eps = 1e-8, weightDecay = 0.01 } = {})",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/TinyModels",
    "path": "agi/reference/particle_voice/risk/TinyModels.md",
    "source": "agi/particle_voice/risk/TinyModels.js",
    "import": "/agi/particle_voice/risk/TinyModels.js",
    "sourceHash": "cfde29bd4820222255a17e49e4d66d8c7fb1246a1116b488b0bacd8458a5dbd5",
    "summary": "TinyModels.js — R7 Phase -1 risk spike model definitions. \"1M-parameter model, 5M Conv1D model, tiny adversarial audio pair\" — particle-voice-26357c.md, Phase -1. Exact param counts land close to but not exactly at 1,000,000/5,000,000 (real counts are reported, not fudged to hit a round number) — the plan's own number is an order-of-magnitude descriptor.",
    "exports": [
      {
        "name": "createTinyMlpModel",
        "kind": "function",
        "signature": "createTinyMlpModel(rng)",
        "summary": "~0.93M-param MLP: Linear(64,650)-ReLU x3 -Linear(650,64)."
      },
      {
        "name": "createTinyConv1dModel",
        "kind": "function",
        "signature": "createTinyConv1dModel(rng, sequenceLength = 16)",
        "summary": "~4.5M-param Conv1D stack: Conv1D(16,384,5)-ReLU, 6x[Conv1D(384,384,5)-ReLU], Conv1D(384,16,5)."
      },
      {
        "name": "createTinyGan",
        "kind": "function",
        "signature": "createTinyGan(rng)",
        "summary": "Tiny generator (noise[8] -> sample[16]) + discriminator (sample[16] -> real-probability)."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/TrainingSpikeProbe",
    "path": "agi/reference/particle_voice/risk/TrainingSpikeProbe.md",
    "source": "agi/particle_voice/risk/TrainingSpikeProbe.js",
    "import": "/agi/particle_voice/risk/TrainingSpikeProbe.js",
    "sourceHash": "34634c487999f5b7f1c22135c140bc03790964bd27e4181e090db8dfdd818309",
    "summary": "TrainingSpikeProbe.js — R7 Phase -1 risk spike orchestrator. Exit gate: \"1M model converges; 5M Conv1D model converges; tiny GAN converges and checkpoints; AdamW memory estimate matches measurement.\" — Phase -1. \"OPFS cache behavior\" and \"device-loss re-upload\" are also named in the risk page description and covered here. Scoping notes (read before trusting a pass): - \"Converges\" here means memorizes a small fixed random input/target pair (loss drops sharply) — a feasibility/mechanics check, not a generalization claim. - \"Device-loss re-upload\" exercises the re-upload PROCEDURE after an intentional device.destroy(); there is no scriptable public API to force an uncontrolled device loss (crash), so that path is not tested.",
    "exports": [
      {
        "name": "runTrainingSpikeProbe",
        "kind": "function",
        "signature": "async runTrainingSpikeProbe()",
        "summary": ""
      },
      {
        "name": "buildTrainingSpikeReceipt",
        "kind": "function",
        "signature": "async buildTrainingSpikeReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareTrainingSpikeReceiptDownload",
        "kind": "function",
        "signature": "prepareTrainingSpikeReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R7_TRAINING_SPIKE_SCHEMA",
        "kind": "constant",
        "signature": "R7_TRAINING_SPIKE_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/VadContinuityProbe",
    "path": "agi/reference/particle_voice/risk/VadContinuityProbe.md",
    "source": "agi/particle_voice/risk/VadContinuityProbe.js",
    "import": "/agi/particle_voice/risk/VadContinuityProbe.js",
    "sourceHash": "52324adc3b4c8c3586f795fbba86bfe2a4e34caf20617ae4d89a2f5d70965f59",
    "summary": "VadContinuityProbe.js — R11 Phase -1 risk spike orchestrator.",
    "exports": [
      {
        "name": "runVadContinuityProbe",
        "kind": "function",
        "signature": "async runVadContinuityProbe()",
        "summary": ""
      },
      {
        "name": "buildVadContinuityReceipt",
        "kind": "function",
        "signature": "async buildVadContinuityReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareVadContinuityReceiptDownload",
        "kind": "function",
        "signature": "prepareVadContinuityReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R11_VAD_CONTINUITY_SCHEMA",
        "kind": "constant",
        "signature": "R11_VAD_CONTINUITY_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/VoiceBoxPhysiologyProbe",
    "path": "agi/reference/particle_voice/risk/VoiceBoxPhysiologyProbe.md",
    "source": "agi/particle_voice/risk/VoiceBoxPhysiologyProbe.js",
    "import": "/agi/particle_voice/risk/VoiceBoxPhysiologyProbe.js",
    "sourceHash": "b2a0067cf8691acb435642bc00cb315d07f58f9e2333f4ef5cd6f785e1244623",
    "summary": "VoiceBoxPhysiologyProbe.js — R6A Phase -1 risk spike orchestrator. Exit gate: \"ArticulationHead -> NeuralPhysiologyState -> BreathReservoir/ParticleTract -> acoustic renderer receipt; physiology intervention changes final PCM.\" — Phase -1. No dedicated filename exists for R6A in the plan's risk/ page list either (same situation as R5) — this page covers that exit gate directly. Wires: createScriptedArticulationHead (stand-in for the trained model) -> NeuralPhysiologyState -> createBreathReservoir -> createGlottalSource + createVocalTract (R6's waveguide, reused directly, not re-implemented) -> PCM. Renders the same scripted utterance twice — once at baseline, once with a reduced-respiratory-drive intervention — and requires the two PCM outputs to measurably differ while both remain stable.",
    "exports": [
      {
        "name": "runVoiceBoxPhysiologyProbe",
        "kind": "function",
        "signature": "async runVoiceBoxPhysiologyProbe()",
        "summary": ""
      },
      {
        "name": "buildVoiceBoxPhysiologyReceipt",
        "kind": "function",
        "signature": "async buildVoiceBoxPhysiologyReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareVoiceBoxPhysiologyReceiptDownload",
        "kind": "function",
        "signature": "prepareVoiceBoxPhysiologyReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R6A_VOICEBOX_PHYSIOLOGY_SCHEMA",
        "kind": "constant",
        "signature": "R6A_VOICEBOX_PHYSIOLOGY_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/VoiceBoxWaveguideProbe",
    "path": "agi/reference/particle_voice/risk/VoiceBoxWaveguideProbe.md",
    "source": "agi/particle_voice/risk/VoiceBoxWaveguideProbe.js",
    "import": "/agi/particle_voice/risk/VoiceBoxWaveguideProbe.js",
    "sourceHash": "fa42835c831bf9082202480a07446c2e7a96dc8d1bade4577a549f93584b189b",
    "summary": "VoiceBoxWaveguideProbe.js — R6 Phase -1 risk spike orchestrator. Exit gate: \"Stable vowels, nasal coupling, closure/release/frication, pitch sweeps with no clicks or energy explosions.\" — Phase -1. Pure JS/CPU (see KellyLochbaumWaveguide.js header for why): this proves the waveguide algorithm itself is stable and produces plausible, controllable behavior. GPU kernel parity (`tract_waveguide.js` etc.) is a separate Phase 2 concern, tested the same way R2 checked matmul parity.",
    "exports": [
      {
        "name": "runVoiceBoxWaveguideProbe",
        "kind": "function",
        "signature": "async runVoiceBoxWaveguideProbe()",
        "summary": ""
      },
      {
        "name": "buildVoiceBoxWaveguideReceipt",
        "kind": "function",
        "signature": "async buildVoiceBoxWaveguideReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareVoiceBoxWaveguideReceiptDownload",
        "kind": "function",
        "signature": "prepareVoiceBoxWaveguideReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R6_VOICEBOX_WAVEGUIDE_SCHEMA",
        "kind": "constant",
        "signature": "R6_VOICEBOX_WAVEGUIDE_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/WeightPagingProbe",
    "path": "agi/reference/particle_voice/risk/WeightPagingProbe.md",
    "source": "agi/particle_voice/risk/WeightPagingProbe.js",
    "import": "/agi/particle_voice/risk/WeightPagingProbe.js",
    "sourceHash": "781fcb693818fca7bf27b02e1954d6b061309a4343fcdbee9521fa919e83e031",
    "summary": "WeightPagingProbe.js — R8 Phase -1 risk spike. \"Weight page download/recovery/page-hash/interrupted-download works; device-loss re-upload succeeds.\" — Phase -1. No dedicated filename exists for R8 in the plan's risk/ page list — named weight-paging.html here. Simulates WeightPageStore.js's core responsibilities: a per-page SHA-256 directory, an interrupted download that must retry (not silently drop data), corruption detection via hash mismatch, and multi-page device-loss re-upload (extends R7's single-buffer version to several pages).",
    "exports": [
      {
        "name": "runWeightPagingProbe",
        "kind": "function",
        "signature": "async runWeightPagingProbe()",
        "summary": ""
      },
      {
        "name": "buildWeightPagingReceipt",
        "kind": "function",
        "signature": "async buildWeightPagingReceipt(result)",
        "summary": ""
      },
      {
        "name": "prepareWeightPagingReceiptDownload",
        "kind": "function",
        "signature": "prepareWeightPagingReceiptDownload(receipt)",
        "summary": ""
      },
      {
        "name": "R8_WEIGHT_PAGING_SCHEMA",
        "kind": "constant",
        "signature": "R8_WEIGHT_PAGING_SCHEMA",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/risk/WolaSignals",
    "path": "agi/reference/particle_voice/risk/WolaSignals.md",
    "source": "agi/particle_voice/risk/WolaSignals.js",
    "import": "/agi/particle_voice/risk/WolaSignals.js",
    "sourceHash": "87aae01bb39ae84e1a9a7530bc64e835f0c3b70616dcac4185f1543eb71ea775",
    "summary": "WolaSignals.js — deterministic test signals, Hann window, and CPU-side sum-of-squares WOLA normalization/overlap-add for R3 (stft-wola.html).",
    "exports": [
      {
        "name": "hannWindow",
        "kind": "function",
        "signature": "hannWindow(N)",
        "summary": ""
      },
      {
        "name": "impulseSignal",
        "kind": "function",
        "signature": "impulseSignal(N)",
        "summary": ""
      },
      {
        "name": "sineSweepSignal",
        "kind": "function",
        "signature": "sineSweepSignal(N, sampleRate, f0, f1)",
        "summary": "Linear chirp from f0 to f1 Hz across N samples at sampleRate."
      },
      {
        "name": "whiteNoiseSignal",
        "kind": "function",
        "signature": "whiteNoiseSignal(N, seed = 7)",
        "summary": ""
      },
      {
        "name": "sineSignal",
        "kind": "function",
        "signature": "sineSignal(length, sampleRate, freqHz, amplitude = 1.0)",
        "summary": ""
      },
      {
        "name": "computeSumOfSquaresEnvelope",
        "kind": "function",
        "signature": "computeSumOfSquaresEnvelope(window, hop, totalLength, numFrames)",
        "summary": "sum_k window[t - k*hop]^2 for every output sample t in [0, totalLength)."
      },
      {
        "name": "overlapAdd",
        "kind": "function",
        "signature": "overlapAdd(frames, hop, totalLength)",
        "summary": "Overlap-add windowed frames (each already synthesis-windowed) into a flat buffer."
      },
      {
        "name": "sliceFrames",
        "kind": "function",
        "signature": "sliceFrames(signal, N, hop, numFrames)",
        "summary": "Slice a flat signal into overlapping N-length frames at the given hop (zero-padded past the end)."
      },
      {
        "name": "applyWindow",
        "kind": "function",
        "signature": "applyWindow(frame, window)",
        "summary": ""
      },
      {
        "name": "maxAbsDiff",
        "kind": "function",
        "signature": "maxAbsDiff(a, b, startIndex = 0, endIndex = a.length)",
        "summary": ""
      },
      {
        "name": "peakAbs",
        "kind": "function",
        "signature": "peakAbs(a, startIndex = 0, endIndex = a.length)",
        "summary": ""
      },
      {
        "name": "toDb",
        "kind": "function",
        "signature": "toDb(ratio)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/GPUReadbackRing",
    "path": "agi/reference/particle_voice/streaming/GPUReadbackRing.md",
    "source": "agi/particle_voice/streaming/GPUReadbackRing.js",
    "import": "/agi/particle_voice/streaming/GPUReadbackRing.js",
    "sourceHash": "ef614f2ef264549bc92360ea15a52ca4af03fbccd68d92d396f9fb6bec598695",
    "summary": "GPUReadbackRing.js — Phase 2 ParticleVoice audio bridge. Production generalization of `risk/GpuReadbackRing.js` (R4)'s proven round-robin `MAP_READ` staging-slot ring — same core technique (each slot is copied into and mapped independently; the ONLY synchronization primitive is that slot's own `mapAsync()` promise, never a global `device.queue.onSubmittedWorkDone()`), generalized from R4's single-fixed-size/offset-0 probe surface to the production needs this module's callers (`ParticleTract.js`'s per-chunk PCM readback, eventually `SharedPCMRing.js`'s producer side) actually have: a byte-offset/length per request (not always the whole buffer from 0), `inFlightCount()` for backpressure introspection instead of only a throw on exhaustion, and GPU buffer accounting via `engine/core/gpu/GpuMetrics.js` (matching `GpuBuffer.js`'s own convention for every other buffer this project creates). R4's exit gate (`ReadbackAudioProbe.js`) already measured this exact technique's p50/p95/p99 readback latency and zero-underrun behavior across an 8-slot ring — this module does not re-derive or change that proven synchronization discipline, only widens its calling surface.",
    "exports": [
      {
        "name": "GPUReadbackRing",
        "kind": "class",
        "signature": "class GPUReadbackRing",
        "summary": ""
      },
      {
        "name": "createGPUReadbackRing",
        "kind": "function",
        "signature": "createGPUReadbackRing(device, slotByteSize, options)",
        "summary": ""
      },
      {
        "name": "DEFAULT_READBACK_SLOT_COUNT",
        "kind": "constant",
        "signature": "DEFAULT_READBACK_SLOT_COUNT",
        "summary": ""
      },
      {
        "name": "MIN_READBACK_SLOT_COUNT",
        "kind": "constant",
        "signature": "MIN_READBACK_SLOT_COUNT",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/PolyphaseResampler",
    "path": "agi/reference/particle_voice/streaming/PolyphaseResampler.md",
    "source": "agi/particle_voice/streaming/PolyphaseResampler.js",
    "import": "/agi/particle_voice/streaming/PolyphaseResampler.js",
    "sourceHash": "cab38b9f0b74b585dec4daf90c92eab6475519c18ce741132ca4d17edd295324",
    "summary": "PolyphaseResampler.js — Phase 2 ParticleVoice audio bridge. \"ParticleVoice's own DSP runs at 32 kHz internally and resamples via `PolyphaseResampler.js` at the audio bridge boundary\" — `ARCHITECTURE.md` §\"Measured lead values\" / `spec/DeviceContract-v0.md` §7 (measured `AudioContext.sampleRate` 48000 Hz on the reference device). No risk-spike precedent exists for this module (checked `risk/` and `engine/` — the only \"resample\" hits in `engine/` are unrelated texture/render resample passes), so this is a fresh CPU-side (not GPU-kernel) implementation — appropriate for the audio bridge, which is already CPU-side per the plan (`SharedPCMRing.js`, `ParticleVoiceProcessor.js`). Classic Crochiere/Rabiner polyphase interpolator-decimator: for an EXACT integer ratio L/M (`inputSampleRate`/`outputSampleRate` reduced by their GCD — both are integers in practice, e.g. 32000 -> 48000 reduces exactly to L=3, M=2), a single windowed-sinc lowpass FIR (cutoff at `min(inputRate, outputRate)/2`, designed at the upsampled L*inputRate rate to suppress both upsampling images and downsampling aliases) is decomposed into L phase sub-filters of `tapsPerPhase` taps each. Per output sample, only `tapsPerPhase` multiplies are needed regardless of L — the O(tapsPerPhase*L) cost is paid once at construction (filter design), not per sample. STREAMING correctness (critical for a chunked audio bridge, where `WaveguideAcousticState` chunks arrive as small windows, not one whole signal): `process()` keeps a persistent tail of not-yet-fully-consumed input samples across calls (`_history`/`_historyBaseIndex`) and a continuously-advancing upsampled-domain position (`_pos`), so `resampler.process(wholeSignal)` and `chunks.map(c => resampler.process(c))` concatenated produce IDENTICAL output — verified directly in `tests/particle-voice/polyphase-resampler.html`. Known limitation (documented, not hidden): the FIR filter's group delay (~`tapsPerPhase/2` input samples) means the first `tapsPerPhase/2`-ish output samples ramp up from a zero-padded history rather than being bit-perfect from sample 0 — a normal, bounded FIR startup transient, not a correctness bug (every streaming FIR filter has this).",
    "exports": [
      {
        "name": "PolyphaseResampler",
        "kind": "class",
        "signature": "class PolyphaseResampler",
        "summary": ""
      },
      {
        "name": "gcd",
        "kind": "function",
        "signature": "gcd(a, b)",
        "summary": "PolyphaseResampler.js — Phase 2 ParticleVoice audio bridge. \"ParticleVoice's own DSP runs at 32 kHz internally and resamples via `PolyphaseResampler.js` at the audio bridge boundary\" — `ARCHITECTURE.md` §\"Measured lead values\" / `spec/DeviceContract-v0.md` §7 (measured `AudioContext.sampleRate` 48000 Hz on the reference device). No risk-spike precedent exists for this module (checked `risk/` and `engine/` — the only \"resample\" hits in `engine/` are unrelated texture/render resample passes), so this is a fresh CPU-side (not GPU-kernel) implementation — appropriate for the audio bridge, which is already CPU-side per the plan (`SharedPCMRing.js`, `ParticleVoiceProcessor.js`). Classic Crochiere/Rabiner polyphase interpolator-decimator: for an EXACT integer ratio L/M (`inputSampleRate`/`outputSampleRate` reduced by their GCD — both are integers in practice, e.g. 32000 -> 48000 reduces exactly to L=3, M=2), a single windowed-sinc lowpass FIR (cutoff at `min(inputRate, outputRate)/2`, designed at the upsampled L*inputRate rate to suppress both upsampling images and downsampling aliases) is decomposed into L phase sub-filters of `tapsPerPhase` taps each. Per output sample, only `tapsPerPhase` multiplies are needed regardless of L — the O(tapsPerPhase*L) cost is paid once at construction (filter design), not per sample. STREAMING correctness (critical for a chunked audio bridge, where `WaveguideAcousticState` chunks arrive as small windows, not one whole signal): `process()` keeps a persistent tail of not-yet-fully-consumed input samples across calls (`_history`/`_historyBaseIndex`) and a continuously-advancing upsampled-domain position (`_pos`), so `resampler.process(wholeSignal)` and `chunks.map(c => resampler.process(c))` concatenated produce IDENTICAL output — verified directly in `tests/particle-voice/polyphase-resampler.html`. Known limitation (documented, not hidden): the FIR filter's group delay (~`tapsPerPhase/2` input samples) means the first `tapsPerPhase/2`-ish output samples ramp up from a zero-padded history rather than being bit-perfect from sample 0 — a normal, bounded FIR startup transient, not a correctness bug (every streaming FIR filter has this)."
      },
      {
        "name": "designWindowedSincLowpass",
        "kind": "function",
        "signature": "designWindowedSincLowpass(numTaps, cutoff)",
        "summary": "Windowed-sinc lowpass FIR, Hann-windowed, normalized to unity DC gain (`sum(h) === 1`). `cutoff` is normalized to the filter's OWN sample rate (0.5 = that rate's Nyquist) — callers designing a polyphase interpolator pass a cutoff normalized to the UPSAMPLED (L*inputRate) rate, per this module's `buildPolyphaseFilter`."
      },
      {
        "name": "buildPolyphaseFilter",
        "kind": "function",
        "signature": "buildPolyphaseFilter(L, M, tapsPerPhase)",
        "summary": "Builds the L-phase decomposition of a full-length windowed-sinc lowpass sized for L/M polyphase interpolation-decimation."
      },
      {
        "name": "DEFAULT_TAPS_PER_PHASE",
        "kind": "constant",
        "signature": "DEFAULT_TAPS_PER_PHASE",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/SafetyLimiter",
    "path": "agi/reference/particle_voice/streaming/SafetyLimiter.md",
    "source": "agi/particle_voice/streaming/SafetyLimiter.js",
    "import": "/agi/particle_voice/streaming/SafetyLimiter.js",
    "sourceHash": "3d2fed75098e26a8be33be3b1455a1569cffb40d96a13c2d30bd38b001f61f05",
    "summary": "SafetyLimiter.js — Phase 2 ParticleVoice audio bridge. \"SafetyLimiter.js — soft clip, DC removal, fade.\" No existing DC-blocker/soft-clip/limiter precedent exists in this codebase to reuse (checked `engine/audio/` — `PatchRunner.worklet.js`'s `distortion` node is a bare `Math.tanh(x*drive)` waveshaper, not a limiter/DC-blocker), so this is a fresh CPU-side implementation. Sits at the very end of the audio bridge, downstream of `PolyphaseResampler.js`, as the last line of defense before PCM reaches `SharedPCMRing.js`/the AudioContext — a physics/neural solver misbehaving upstream (an unstable `tract_waveguide.js` reflection coefficient, a runaway `glottal_lf.js` Newton-Raphson solve, etc.) should never be able to produce a harsh digital-clip transient, a DC offset, or a click at an utterance boundary — that is this module's entire job. Three independent, composable stages, applied per-sample in this order: 1. DC removal — the standard DC-blocking filter (Julius O. Smith's well-known form): `y[n] = x[n] - x[n-1] + R*y[n-1]`. At DC this converges to exactly 0 (the `z=1` zero cancels any constant component); `R` close to 1 keeps the notch narrow so audible low frequencies are essentially untouched. `R` is derived via this codebase's own established exponential-pole convention (`risk/BreathReservoir.js`'s `lagRate = 1 - exp(-1/(tau*sampleRate))` pattern) rather than inventing a new formula style: here `R = exp(-2*pi*cornerHz/sampleRate)`. 2. Fade — an explicit, persistent linear gain ramp (`beginFadeIn`/`beginFadeOut`), so an utterance start/stop or a cancellation (`SharedPCMRing.js`'s `resetSharedPCMRing()` discontinuity) can be given a clean few-millisecond ramp instead of an audible click. 3. Soft clip — a `tanh`-saturating soft knee above `softClipThreshold` (default 0.8): transparent (identity) below the threshold, then a C1-continuous (matching slope at the knee, so no audible kink) saturating curve that asymptotically approaches +/-1 but never reaches or exceeds it — i.e. this NEVER hard-clips, by construction. All three stages carry PERSISTENT state across `process()` calls (DC filter memory, fade envelope position) — `reset()` clears all of it in one call, mirroring `SharedPCMRing.js`/`PolyphaseResampler.js`'s reset-on-discontinuity convention, so a caller can reset the whole audio bridge chain in lockstep on cancellation/interruption.",
    "exports": [
      {
        "name": "SafetyLimiter",
        "kind": "class",
        "signature": "class SafetyLimiter",
        "summary": ""
      },
      {
        "name": "softClip",
        "kind": "function",
        "signature": "softClip(x, threshold = DEFAULT_SOFT_CLIP_THRESHOLD)",
        "summary": "Pure, stateless: `tanh`-saturating soft knee. Identity below `threshold` in magnitude; above it, asymptotically approaches `+/-1` but never reaches or exceeds it. C1-continuous at the knee (`tanh'(0) === 1` matches the identity segment's slope exactly)."
      },
      {
        "name": "DEFAULT_DC_BLOCK_CORNER_HZ",
        "kind": "constant",
        "signature": "DEFAULT_DC_BLOCK_CORNER_HZ",
        "summary": "SafetyLimiter.js — Phase 2 ParticleVoice audio bridge. \"SafetyLimiter.js — soft clip, DC removal, fade.\" No existing DC-blocker/soft-clip/limiter precedent exists in this codebase to reuse (checked `engine/audio/` — `PatchRunner.worklet.js`'s `distortion` node is a bare `Math.tanh(x*drive)` waveshaper, not a limiter/DC-blocker), so this is a fresh CPU-side implementation. Sits at the very end of the audio bridge, downstream of `PolyphaseResampler.js`, as the last line of defense before PCM reaches `SharedPCMRing.js`/the AudioContext — a physics/neural solver misbehaving upstream (an unstable `tract_waveguide.js` reflection coefficient, a runaway `glottal_lf.js` Newton-Raphson solve, etc.) should never be able to produce a harsh digital-clip transient, a DC offset, or a click at an utterance boundary — that is this module's entire job. Three independent, composable stages, applied per-sample in this order: 1. DC removal — the standard DC-blocking filter (Julius O. Smith's well-known form): `y[n] = x[n] - x[n-1] + R*y[n-1]`. At DC this converges to exactly 0 (the `z=1` zero cancels any constant component); `R` close to 1 keeps the notch narrow so audible low frequencies are essentially untouched. `R` is derived via this codebase's own established exponential-pole convention (`risk/BreathReservoir.js`'s `lagRate = 1 - exp(-1/(tau*sampleRate))` pattern) rather than inventing a new formula style: here `R = exp(-2*pi*cornerHz/sampleRate)`. 2. Fade — an explicit, persistent linear gain ramp (`beginFadeIn`/`beginFadeOut`), so an utterance start/stop or a cancellation (`SharedPCMRing.js`'s `resetSharedPCMRing()` discontinuity) can be given a clean few-millisecond ramp instead of an audible click. 3. Soft clip — a `tanh`-saturating soft knee above `softClipThreshold` (default 0.8): transparent (identity) below the threshold, then a C1-continuous (matching slope at the knee, so no audible kink) saturating curve that asymptotically approaches +/-1 but never reaches or exceeds it — i.e. this NEVER hard-clips, by construction. All three stages carry PERSISTENT state across `process()` calls (DC filter memory, fade envelope position) — `reset()` clears all of it in one call, mirroring `SharedPCMRing.js`/`PolyphaseResampler.js`'s reset-on-discontinuity convention, so a caller can reset the whole audio bridge chain in lockstep on cancellation/interruption."
      },
      {
        "name": "DEFAULT_SOFT_CLIP_THRESHOLD",
        "kind": "constant",
        "signature": "DEFAULT_SOFT_CLIP_THRESHOLD",
        "summary": ""
      },
      {
        "name": "DEFAULT_FADE_MS",
        "kind": "constant",
        "signature": "DEFAULT_FADE_MS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/SharedPCMRing",
    "path": "agi/reference/particle_voice/streaming/SharedPCMRing.md",
    "source": "agi/particle_voice/streaming/SharedPCMRing.js",
    "import": "/agi/particle_voice/streaming/SharedPCMRing.js",
    "sourceHash": "cd27717ce41748bd7683f89d36fd0fe5761c9dd55ef57f4ca937e3bd7379727e",
    "summary": "SharedPCMRing.js — Phase 2 ParticleVoice audio bridge. Production generalization of `risk/ReadbackAudioProbe.js` (R4)'s proven `createSharedRing`/`prefillRing`/`startProducer` SharedArrayBuffer PCM ring (single-producer/single-consumer, `[writeIndex, readIndex, underrunCount, processCallCount]` control layout consumed by `R4ReadbackAudioWorklet.worklet.js`'s allocation-free `process()`). Adds the plan's explicit \"generation/sequence IDs\" requirement, which R4's risk spike did not need (a disposable probe never gets reset mid-run): - `generation` (control[4]): bumped by `resetSharedPCMRing()` whenever the producer discards the ring's contents outright (cancellation/ interruption per the plan's Phase 5 \"cancellation resets state cleanly\", or a device-loss GPU recovery restart). The CONSUMER side (`readCurrentGeneration`/the resync check in `worklet/ParticleVoiceProcessor.js`) compares its own cached generation against this value every `process()` call; on mismatch it resyncs `readIndex` to the CURRENT `writeIndex` (dropping whatever stale audio was in the ring before the reset) rather than trusting a `readIndex` that may now point at samples from a discarded utterance. - `writeSequence` (control[5]): incremented once per producer `writeSharedPCMRing()` call (a monotonic chunk counter, not a sample counter) — correlates a ring write with the `WaveguideAcousticState.sampleIndex` that produced it for diagnostics/telemetry; the consumer does not need to read it for correctness (unlike `generation`). The reader-side helper function here (`readAvailable`) is pure arithmetic over caller-supplied typed arrays — no allocation, no closures over per-call state — usable from a non-worklet consumer (e.g. a test harness) without violating an allocation-free constraint. `worklet/ParticleVoiceProcessor.js` (the AudioWorkletProcessor that actually drains this ring in real-time) does NOT import this module — both existing worklet files in this project (`engine/audio/synth/PatchRunner.worklet.js`, `risk/R4ReadbackAudioWorklet.worklet.js`) are self-contained with no ES imports, so `ParticleVoiceProcessor.js` duplicates `CONTROL_INDEX` and the read-position arithmetic inline rather than relying on an unverified assumption that `AudioWorkletGlobalScope` module imports behave identically to this project's other worklet loading paths. Both files' docstrings flag this duplication explicitly so the two `CONTROL_INDEX` definitions cannot silently drift apart unnoticed.",
    "exports": [
      {
        "name": "createSharedPCMRing",
        "kind": "function",
        "signature": "createSharedPCMRing({ sampleRate, capacitySeconds = DEFAULT_RING_CAPACITY_SECONDS })",
        "summary": ""
      },
      {
        "name": "writeSharedPCMRing",
        "kind": "function",
        "signature": "writeSharedPCMRing(ring, samples)",
        "summary": "Producer-side write: copies `samples` into the ring starting at the current `writeIndex`, advancing it and incrementing `writeSequence`. Clamps to available free space (`capacity - 1 - available`, the usual ring-buffer one-slot gap to distinguish full from empty) rather than overflowing past the reader's current position and corrupting not-yet-read samples — R4's own `startProducer` avoided this by pacing writes to wall-clock time so it could never get ahead of the ring's capacity, but a production writer driven by GPU chunk completion (irregular timing) cannot assume that pacing, so this function must be defensive."
      },
      {
        "name": "resetSharedPCMRing",
        "kind": "function",
        "signature": "resetSharedPCMRing(ring)",
        "summary": "Producer-side reset: zeroes both indices and bumps `generation` so the consumer discards its cached read position on its next `process()` call rather than replaying stale audio. Does not zero the PCM data itself (unnecessary — the consumer will resync past it)."
      },
      {
        "name": "readAvailable",
        "kind": "function",
        "signature": "readAvailable(control, capacity)",
        "summary": "Pure, allocation-free: samples currently available to read (does not mutate anything). Safe to call from `process()`."
      },
      {
        "name": "CONTROL_INDEX",
        "kind": "constant",
        "signature": "CONTROL_INDEX",
        "summary": "SharedPCMRing.js — Phase 2 ParticleVoice audio bridge. Production generalization of `risk/ReadbackAudioProbe.js` (R4)'s proven `createSharedRing`/`prefillRing`/`startProducer` SharedArrayBuffer PCM ring (single-producer/single-consumer, `[writeIndex, readIndex, underrunCount, processCallCount]` control layout consumed by `R4ReadbackAudioWorklet.worklet.js`'s allocation-free `process()`). Adds the plan's explicit \"generation/sequence IDs\" requirement, which R4's risk spike did not need (a disposable probe never gets reset mid-run): - `generation` (control[4]): bumped by `resetSharedPCMRing()` whenever the producer discards the ring's contents outright (cancellation/ interruption per the plan's Phase 5 \"cancellation resets state cleanly\", or a device-loss GPU recovery restart). The CONSUMER side (`readCurrentGeneration`/the resync check in `worklet/ParticleVoiceProcessor.js`) compares its own cached generation against this value every `process()` call; on mismatch it resyncs `readIndex` to the CURRENT `writeIndex` (dropping whatever stale audio was in the ring before the reset) rather than trusting a `readIndex` that may now point at samples from a discarded utterance. - `writeSequence` (control[5]): incremented once per producer `writeSharedPCMRing()` call (a monotonic chunk counter, not a sample counter) — correlates a ring write with the `WaveguideAcousticState.sampleIndex` that produced it for diagnostics/telemetry; the consumer does not need to read it for correctness (unlike `generation`). The reader-side helper function here (`readAvailable`) is pure arithmetic over caller-supplied typed arrays — no allocation, no closures over per-call state — usable from a non-worklet consumer (e.g. a test harness) without violating an allocation-free constraint. `worklet/ParticleVoiceProcessor.js` (the AudioWorkletProcessor that actually drains this ring in real-time) does NOT import this module — both existing worklet files in this project (`engine/audio/synth/PatchRunner.worklet.js`, `risk/R4ReadbackAudioWorklet.worklet.js`) are self-contained with no ES imports, so `ParticleVoiceProcessor.js` duplicates `CONTROL_INDEX` and the read-position arithmetic inline rather than relying on an unverified assumption that `AudioWorkletGlobalScope` module imports behave identically to this project's other worklet loading paths. Both files' docstrings flag this duplication explicitly so the two `CONTROL_INDEX` definitions cannot silently drift apart unnoticed."
      },
      {
        "name": "CONTROL_SLOT_COUNT",
        "kind": "constant",
        "signature": "CONTROL_SLOT_COUNT",
        "summary": ""
      },
      {
        "name": "DEFAULT_RING_CAPACITY_SECONDS",
        "kind": "constant",
        "signature": "DEFAULT_RING_CAPACITY_SECONDS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/VoiceChunker",
    "path": "agi/reference/particle_voice/streaming/VoiceChunker.md",
    "source": "agi/particle_voice/streaming/VoiceChunker.js",
    "import": "/agi/particle_voice/streaming/VoiceChunker.js",
    "sourceHash": "9040300c209ffc11c5a59eec53c97faad951fdd844ca4c1c172c507d5bd62932",
    "summary": "VoiceChunker.js — Phase 2 ParticleVoice audio bridge. \"VoiceChunker.js — legal buckets: first 12/16/24 frames, steady 32/48/64 frames, 16-frame left context, 8-12 frame overlap.\" A \"frame\" here is the Activity frame size (`ARCHITECTURE.md`'s measured-lead-values table: 256 samples @ 32 kHz, 8 ms/frame) — NOT the 100 Hz plan-level analysis frame rate, which `LengthRegulator.js` (Phase 3) reconciles separately. `spec/PRNN-v0.md` §4 (\"Shape buckets\") directly reuses these exact bucket definitions so a compiled model's execution plan only needs to support this small fixed set of shapes rather than arbitrary sequence lengths — this module is the single source of truth those buckets come from, not a second independent copy of them: firstChunkFrames:  [12, 16, 24] steadyChunkFrames: [32, 48, 64] leftContextFrames: 16 overlapFrames:     [8, 12] `risk/StreamingContinuityProbe.js` (R5) already exercised exactly these bucket sizes (`[12, 16, 24, 32, 48, 64]`) as one of its chunk-boundary patterns and proved that an ABSOLUTE (not chunk-relative) sample/phase counter design makes chunking bit-identical to processing a whole signal at once — this module's `totalFramesEmitted` bookkeeping exists for exactly that reason (a caller can derive an absolute frame index from it), not merely as a diagnostic counter. SCOPE (Phase 2, before any real streaming model exists): this module only decides CHUNK SIZE SCHEDULING (which legal bucket to use next, given how many frames are currently available) — it does not itself run a model, apply left-context frames to a real tensor, or overlap-add output audio (that's `overlap_add.js`/`crossfade.js`, already built and verified in Phase 2's DSP kernels; a future model-execution layer composes THOSE with the chunk-size decisions THIS module makes). Latency/deadline tradeoffs (when to prefer a smaller, faster-available bucket over waiting for a larger one) are explicitly `VoiceDeadlineScheduler.js`'s job, not this module's — `nextChunk()` here always greedily picks the LARGEST legal bucket that currently fits, which is the natural default in the absence of an explicit deadline override.",
    "exports": [
      {
        "name": "VoiceChunker",
        "kind": "class",
        "signature": "class VoiceChunker",
        "summary": ""
      },
      {
        "name": "FIRST_CHUNK_FRAME_BUCKETS",
        "kind": "constant",
        "signature": "FIRST_CHUNK_FRAME_BUCKETS",
        "summary": "VoiceChunker.js — Phase 2 ParticleVoice audio bridge. \"VoiceChunker.js — legal buckets: first 12/16/24 frames, steady 32/48/64 frames, 16-frame left context, 8-12 frame overlap.\" A \"frame\" here is the Activity frame size (`ARCHITECTURE.md`'s measured-lead-values table: 256 samples @ 32 kHz, 8 ms/frame) — NOT the 100 Hz plan-level analysis frame rate, which `LengthRegulator.js` (Phase 3) reconciles separately. `spec/PRNN-v0.md` §4 (\"Shape buckets\") directly reuses these exact bucket definitions so a compiled model's execution plan only needs to support this small fixed set of shapes rather than arbitrary sequence lengths — this module is the single source of truth those buckets come from, not a second independent copy of them: firstChunkFrames:  [12, 16, 24] steadyChunkFrames: [32, 48, 64] leftContextFrames: 16 overlapFrames:     [8, 12] `risk/StreamingContinuityProbe.js` (R5) already exercised exactly these bucket sizes (`[12, 16, 24, 32, 48, 64]`) as one of its chunk-boundary patterns and proved that an ABSOLUTE (not chunk-relative) sample/phase counter design makes chunking bit-identical to processing a whole signal at once — this module's `totalFramesEmitted` bookkeeping exists for exactly that reason (a caller can derive an absolute frame index from it), not merely as a diagnostic counter. SCOPE (Phase 2, before any real streaming model exists): this module only decides CHUNK SIZE SCHEDULING (which legal bucket to use next, given how many frames are currently available) — it does not itself run a model, apply left-context frames to a real tensor, or overlap-add output audio (that's `overlap_add.js`/`crossfade.js`, already built and verified in Phase 2's DSP kernels; a future model-execution layer composes THOSE with the chunk-size decisions THIS module makes). Latency/deadline tradeoffs (when to prefer a smaller, faster-available bucket over waiting for a larger one) are explicitly `VoiceDeadlineScheduler.js`'s job, not this module's — `nextChunk()` here always greedily picks the LARGEST legal bucket that currently fits, which is the natural default in the absence of an explicit deadline override."
      },
      {
        "name": "STEADY_CHUNK_FRAME_BUCKETS",
        "kind": "constant",
        "signature": "STEADY_CHUNK_FRAME_BUCKETS",
        "summary": ""
      },
      {
        "name": "LEFT_CONTEXT_FRAMES",
        "kind": "constant",
        "signature": "LEFT_CONTEXT_FRAMES",
        "summary": ""
      },
      {
        "name": "OVERLAP_FRAME_BUCKETS",
        "kind": "constant",
        "signature": "OVERLAP_FRAME_BUCKETS",
        "summary": ""
      },
      {
        "name": "DEFAULT_OVERLAP_FRAMES",
        "kind": "constant",
        "signature": "DEFAULT_OVERLAP_FRAMES",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/VoiceDeadlineScheduler",
    "path": "agi/reference/particle_voice/streaming/VoiceDeadlineScheduler.md",
    "source": "agi/particle_voice/streaming/VoiceDeadlineScheduler.js",
    "import": "/agi/particle_voice/streaming/VoiceDeadlineScheduler.js",
    "sourceHash": "d5f19b8b05d4c3bbf56ca83b1ab5a36d630931026b0dbea4620ba66c9faed5ee",
    "summary": "VoiceDeadlineScheduler.js — Phase 2 ParticleVoice audio bridge. \"VoiceDeadlineScheduler.js — target >=300 ms, warning 200 ms, emergency 100 ms; GPU budget admission.\" These three thresholds classify how much PLAYBACK RUNWAY remains — how far ahead of the AudioContext's current playback position the produced-but-not-yet-played PCM currently extends (`bufferedAheadMs`, typically derived from `SharedPCMRing.js`'s `readAvailable()` via `bufferedAheadMsFromRing()` below) — into an urgency tier, and maps that tier onto EXISTING scheduling primitives rather than inventing new ones: - `NeuralScheduler.js`'s `'high'|'normal'|'low'` priority buckets (queue ordering among pending `ParticleProgram` runs). - `engine/core/gpu/GpuFrameBudgetBroker.js`'s `workClass === 'render-critical'` bypass (`beforeSubmit()`'s own docstring: \"Render- critical work bypasses the budget\") — this IS the \"GPU budget admission\" the plan names: in the `emergency` tier, this module recommends `workClass: 'render-critical'` so the broker admits the work immediately regardless of the current frame's compute budget, rather than letting a nearly-starved voice ring wait behind unrelated background GPU work. Tier boundaries (this module's own `v0` interpretation of the plan's three named thresholds — documented explicitly since the plan states three thresholds but only two transitions are unambiguous from the wording alone): bufferedAheadMs >= 300         -> 'target'      (healthy, target met) 200 <= bufferedAheadMs < 300   -> 'belowTarget'  (below target, not yet urgent) 100 <= bufferedAheadMs < 200   -> 'warning' bufferedAheadMs < 100          -> 'emergency'",
    "exports": [
      {
        "name": "VoiceDeadlineScheduler",
        "kind": "class",
        "signature": "class VoiceDeadlineScheduler",
        "summary": ""
      },
      {
        "name": "classifyDeadlineTier",
        "kind": "function",
        "signature": "classifyDeadlineTier(bufferedAheadMs)",
        "summary": ""
      },
      {
        "name": "admissionFor",
        "kind": "function",
        "signature": "admissionFor(bufferedAheadMs)",
        "summary": ""
      },
      {
        "name": "bufferedAheadMsFromRing",
        "kind": "function",
        "signature": "bufferedAheadMsFromRing(ring)",
        "summary": "Composes with `SharedPCMRing.js`'s `readAvailable()` — the natural real-world source of `bufferedAheadMs` for a live audio bridge — rather than re-deriving ring-position arithmetic here."
      },
      {
        "name": "TARGET_BUFFERED_MS",
        "kind": "constant",
        "signature": "TARGET_BUFFERED_MS",
        "summary": ""
      },
      {
        "name": "WARNING_BUFFERED_MS",
        "kind": "constant",
        "signature": "WARNING_BUFFERED_MS",
        "summary": ""
      },
      {
        "name": "EMERGENCY_BUFFERED_MS",
        "kind": "constant",
        "signature": "EMERGENCY_BUFFERED_MS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/streaming/VoicePlayback",
    "path": "agi/reference/particle_voice/streaming/VoicePlayback.md",
    "source": "agi/particle_voice/streaming/VoicePlayback.js",
    "import": "/agi/particle_voice/streaming/VoicePlayback.js",
    "sourceHash": "413ad1fdadff4c8f7cfdf8d4274f8f0607fbbbbe1bed35a7709cc255cf257a6b",
    "summary": "VoicePlayback.js — Phase 3 ParticleVoice audio bridge. The last hop of the Phase 3 path: model PCM → resample → `SharedPCMRing` → `AudioWorklet` → audible speech. Every piece it composes was built and verified in Phase 2; this module only wires them, which is why it is small. ParticleVoiceModel (32 kHz) → PolyphaseResampler (32 kHz → AudioContext.sampleRate) → SafetyLimiter is already applied upstream by the model → SharedPCMRing (SAB, single-producer/single-consumer) → ParticleVoiceProcessor (AudioWorkletProcessor) ## The resample is mandatory, not optional The model's internal DSP rate is 32 kHz (R6's proven `SAMPLE_RATE`), but an `AudioContext` picks its own rate — commonly 48 kHz, sometimes 44.1 kHz. Writing 32 kHz samples into a 48 kHz ring without resampling does not error: it plays back **1.5× too fast and a fifth too high**, which sounds like a chipmunk rather than like a bug. `PolyphaseResampler` handles the exact-integer L/M case (32k→48k reduces to L=3, M=2) and is streaming-correct across chunk boundaries, so feeding it per-utterance or per-chunk gives identical output. ## Backpressure is the producer's problem `writeSharedPCMRing()` CLAMPS to available space and returns how much it actually accepted — it never overwrites unread audio. A producer that ignores the return value silently drops the tail of every utterance that does not fit. `enqueue()` therefore loops, awaiting drain, until everything has been accepted, and reports the total written so a caller can assert it. ## What this module deliberately does NOT do It does not own the `AudioContext`. Creating and resuming one requires a user gesture in a real page, its lifetime belongs to the application, and a test harness needs to build its own — so the context and worklet node are passed in via `attach()`. That also keeps this module testable without assuming any particular page structure.",
    "exports": [
      {
        "name": "VoicePlayback",
        "kind": "class",
        "signature": "class VoicePlayback",
        "summary": ""
      },
      {
        "name": "createVoicePlayback",
        "kind": "function",
        "signature": "createVoicePlayback(options)",
        "summary": ""
      },
      {
        "name": "DEFAULT_PLAYBACK_CONFIG",
        "kind": "constant",
        "signature": "DEFAULT_PLAYBACK_CONFIG",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "particle_voice/worklet/ParticleVoiceProcessor",
    "path": "agi/reference/particle_voice/worklet/ParticleVoiceProcessor.md",
    "source": "agi/particle_voice/worklet/ParticleVoiceProcessor.js",
    "import": "/agi/particle_voice/worklet/ParticleVoiceProcessor.js",
    "sourceHash": "d5ddc22ad0776a4f1c4ebf078bc9a26ae840576f57ee6079f35f6f917435bb04",
    "summary": "ParticleVoiceProcessor.js — Phase 2 ParticleVoice audio bridge. Production AudioWorkletProcessor consuming `SharedPCMRing.js`'s ring, generalizing `risk/R4ReadbackAudioWorklet.worklet.js` (R4)'s proven allocation-free `process()` (no `new`, no object/array literals, no `await`, no `throw`, no `JSON` — only typed-array indexing and Atomics on pre-existing SharedArrayBuffer-backed views) with the plan's generation-aware resync requirement `SharedPCMRing.js`'s docstring describes. This file is deliberately SELF-CONTAINED (no ES module imports), matching this project's two existing worklet files (`engine/audio/synth/PatchRunner.worklet.js`, `risk/R4ReadbackAudioWorklet.worklet.js`) — `SharedPCMRing.js`'s `CONTROL_INDEX` constants are duplicated here as `CONTROL_INDEX` rather than imported; the two must be kept in sync by hand (flagged in both files' docstrings) rather than relying on an unverified assumption that `AudioWorkletGlobalScope` module imports behave identically to this project's other worklet loading paths. Control layout (Int32Array, SharedArrayBuffer, 6 x int32) — MUST match `SharedPCMRing.js`'s `CONTROL_INDEX` exactly: [0] writeIndex        — next PCM slot the producer will write [1] readIndex         — next PCM slot this processor will read [2] underrunCount     — incremented whenever fewer than one quantum of samples are available [3] processCallCount  — incremented every process() call [4] generation        — bumped by the producer's resetSharedPCMRing(); on change, this processor jumps readIndex to the CURRENT writeIndex (dropping stale pre-reset audio) instead of continuing from a readIndex that may now point at discarded samples. [5] writeSequence     — producer chunk counter; read-only from here, not required for correctness, exposed for parity with SharedPCMRing.js's layout only.",
    "exports": []
  },
  {
    "schemaVersion": 1,
    "title": "persistence/CheckpointContracts",
    "path": "agi/reference/persistence/CheckpointContracts.md",
    "source": "agi/persistence/CheckpointContracts.js",
    "import": "/agi/persistence/CheckpointContracts.js",
    "sourceHash": "7d70b1dee5821f3e9c5445cf8a6b3a3a24d00ea2d860f754ec182b9c297225e6",
    "summary": "Accept legacy {config,parameters} documents and return canonical v1 data.",
    "exports": [
      {
        "name": "createNetworkEnvelope",
        "kind": "function",
        "signature": "createNetworkEnvelope(kind, config, parameters)",
        "summary": ""
      },
      {
        "name": "prepareNetworkEnvelope",
        "kind": "function",
        "signature": "prepareNetworkEnvelope(input, expectedKind)",
        "summary": "Accept legacy {config,parameters} documents and return canonical v1 data."
      },
      {
        "name": "createCheckpointEnvelope",
        "kind": "function",
        "signature": "createCheckpointEnvelope(data)",
        "summary": ""
      },
      {
        "name": "prepareCheckpointEnvelope",
        "kind": "function",
        "signature": "prepareCheckpointEnvelope(input)",
        "summary": "Validate both canonical v1 and the pre-envelope checkpoint shape."
      },
      {
        "name": "AGI_NETWORK_SCHEMA",
        "kind": "constant",
        "signature": "AGI_NETWORK_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_CHECKPOINT_SCHEMA",
        "kind": "constant",
        "signature": "AGI_CHECKPOINT_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_EXPORT_SCHEMA",
        "kind": "constant",
        "signature": "AGI_TRAINING_EXPORT_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_CURRICULUM_STATE_SCHEMA",
        "kind": "constant",
        "signature": "AGI_CURRICULUM_STATE_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_TRAINING_METRICS_SCHEMA",
        "kind": "constant",
        "signature": "AGI_TRAINING_METRICS_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_SCHEMA_VERSION",
        "kind": "constant",
        "signature": "AGI_SCHEMA_VERSION",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "rig/BrainVisualizer",
    "path": "agi/reference/rig/BrainVisualizer.md",
    "source": "agi/rig/BrainVisualizer.js",
    "import": "/agi/rig/BrainVisualizer.js",
    "sourceHash": "cdcb1089acc0790674eb8ce4beaf1ff1e2b3a481c3ebc9d299683df93df58350",
    "summary": "Brain Visualizer Visualizes neural network activity on the brain sphere",
    "exports": [
      {
        "name": "BrainVisualizer",
        "kind": "class",
        "signature": "class BrainVisualizer",
        "summary": "Brain Visualizer Visualizes neural network activity on the brain sphere"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "rig/InjectionSystem",
    "path": "agi/reference/rig/InjectionSystem.md",
    "source": "agi/rig/InjectionSystem.js",
    "import": "/agi/rig/InjectionSystem.js",
    "sourceHash": "b384b97f4e6e45069982e5f1d20f80fc59c23759da7c87a7750f211676f93fd2",
    "summary": "Injection System Manages tentacle attachment from brain to ragdoll bones",
    "exports": [
      {
        "name": "InjectionSystem",
        "kind": "class",
        "signature": "class InjectionSystem",
        "summary": "Injection System Manages tentacle attachment from brain to ragdoll bones"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "rig/ParasiteRig",
    "path": "agi/reference/rig/ParasiteRig.md",
    "source": "agi/rig/ParasiteRig.js",
    "import": "/agi/rig/ParasiteRig.js",
    "sourceHash": "8b107096e5395f4c85a419c851ffa4ba049f54b9842026891b7edd245b1b0857",
    "summary": "Parasite Rig The \"brain sphere\" entity that hovers above and controls the ragdoll",
    "exports": [
      {
        "name": "ParasiteRig",
        "kind": "class",
        "signature": "class ParasiteRig",
        "summary": "Parasite Rig The \"brain sphere\" entity that hovers above and controls the ragdoll"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "rig/TentacleRenderer",
    "path": "agi/reference/rig/TentacleRenderer.md",
    "source": "agi/rig/TentacleRenderer.js",
    "import": "/agi/rig/TentacleRenderer.js",
    "sourceHash": "58066109901eef62fbf56d532a1df8cbd13b373f11e48c0ed9ef78fd44659228",
    "summary": "Tentacle Renderer Renders tentacles using lightning/electricity effect",
    "exports": [
      {
        "name": "TentacleRenderer",
        "kind": "class",
        "signature": "class TentacleRenderer",
        "summary": "Tentacle Renderer Renders tentacles using lightning/electricity effect"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "scene/DebugVisualizer",
    "path": "agi/reference/scene/DebugVisualizer.md",
    "source": "agi/scene/DebugVisualizer.js",
    "import": "/agi/scene/DebugVisualizer.js",
    "sourceHash": "964f8fc855e559602a3d576c2f475f7e17fd61b5c3233239300f28b5293c33ee",
    "summary": "Debug Visualizer Renders debug overlays for training visualization",
    "exports": [
      {
        "name": "DebugVisualizer",
        "kind": "class",
        "signature": "class DebugVisualizer",
        "summary": "Debug Visualizer Renders debug overlays for training visualization"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "scene/GroundPlane",
    "path": "agi/reference/scene/GroundPlane.md",
    "source": "agi/scene/GroundPlane.js",
    "import": "/agi/scene/GroundPlane.js",
    "sourceHash": "e9284e9b81cb3630c56999d8fd7b8f305556d7614361ba66529aa1f1ac8fa659",
    "summary": "Ground Plane Infinite grid ground for training environment",
    "exports": [
      {
        "name": "GroundPlane",
        "kind": "class",
        "signature": "class GroundPlane",
        "summary": "Ground Plane Infinite grid ground for training environment"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "scene/SceneRenderer",
    "path": "agi/reference/scene/SceneRenderer.md",
    "source": "agi/scene/SceneRenderer.js",
    "import": "/agi/scene/SceneRenderer.js",
    "sourceHash": "25abe7add02fc13cfdac49e71c33230d25fff79e187fc5cdd5a40c7f75ecae0c",
    "summary": "",
    "exports": [
      {
        "name": "SceneRenderer",
        "kind": "class",
        "signature": "class SceneRenderer",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "scene/TrackingCamera",
    "path": "agi/reference/scene/TrackingCamera.md",
    "source": "agi/scene/TrackingCamera.js",
    "import": "/agi/scene/TrackingCamera.js",
    "sourceHash": "fe8be2ef8487fed5e007c74ca1cd325d043db89f45e490e4e8bfb8f83e34d8ae",
    "summary": "Tracking Camera Smooth camera that follows the ragdoll",
    "exports": [
      {
        "name": "TrackingCamera",
        "kind": "class",
        "signature": "class TrackingCamera",
        "summary": "Tracking Camera Smooth camera that follows the ragdoll"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "scene/TrainingScene",
    "path": "agi/reference/scene/TrainingScene.md",
    "source": "agi/scene/TrainingScene.js",
    "import": "/agi/scene/TrainingScene.js",
    "sourceHash": "bf308868a8cccca4f34704c445fed5b2ed881c21ec03b35662eedbe93c6381ce",
    "summary": "",
    "exports": [
      {
        "name": "TrainingScene",
        "kind": "class",
        "signature": "class TrainingScene",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "studio/core/StudioApp",
    "path": "agi/reference/studio/core/StudioApp.md",
    "source": "agi/studio/core/StudioApp.js",
    "import": "/agi/studio/core/StudioApp.js",
    "sourceHash": "ae3cf3abd3d8ad473bed34d84e4bf5359cacf1a75287d9d38ffa028e5f782c04",
    "summary": "Studio Application Main AGI Studio workspace application",
    "exports": [
      {
        "name": "StudioApp",
        "kind": "class",
        "signature": "class StudioApp",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "studio/core/StudioDirectoryHandleStore",
    "path": "agi/reference/studio/core/StudioDirectoryHandleStore.md",
    "source": "agi/studio/core/StudioDirectoryHandleStore.js",
    "import": "/agi/studio/core/StudioDirectoryHandleStore.js",
    "sourceHash": "c953ece45ab667f0812f2fc48f698e643c754c7f7a308c7f4331aa3b5017589d",
    "summary": "Versioned IndexedDB owner for AGI Studio directory handles.",
    "exports": [
      {
        "name": "AGIStudioHandleStoreError",
        "kind": "class",
        "signature": "class AGIStudioHandleStoreError extends Error",
        "summary": ""
      },
      {
        "name": "StudioDirectoryHandleStore",
        "kind": "class",
        "signature": "class StudioDirectoryHandleStore",
        "summary": ""
      },
      {
        "name": "isAGIStudioDirectoryHandle",
        "kind": "function",
        "signature": "isAGIStudioDirectoryHandle(value)",
        "summary": ""
      },
      {
        "name": "agiStudioDirectoryHandleCurrentKey",
        "kind": "function",
        "signature": "agiStudioDirectoryHandleCurrentKey(slot)",
        "summary": ""
      },
      {
        "name": "prepareAGIStudioDirectoryHandleRecord",
        "kind": "function",
        "signature": "prepareAGIStudioDirectoryHandleRecord(value, expectedSlot)",
        "summary": "Validate a raw v1 handle or exact v2 envelope without serializing the handle."
      },
      {
        "name": "AGI_STUDIO_HANDLE_DB_NAME",
        "kind": "constant",
        "signature": "AGI_STUDIO_HANDLE_DB_NAME",
        "summary": "Versioned IndexedDB owner for AGI Studio directory handles."
      },
      {
        "name": "AGI_STUDIO_HANDLE_STORE_NAME",
        "kind": "constant",
        "signature": "AGI_STUDIO_HANDLE_STORE_NAME",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_HANDLE_DB_VERSION",
        "kind": "constant",
        "signature": "AGI_STUDIO_HANDLE_DB_VERSION",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_DIRECTORY_HANDLE_SCHEMA",
        "kind": "constant",
        "signature": "AGI_STUDIO_DIRECTORY_HANDLE_SCHEMA",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_DIRECTORY_HANDLE_FORMAT",
        "kind": "constant",
        "signature": "AGI_STUDIO_DIRECTORY_HANDLE_FORMAT",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_DIRECTORY_HANDLE_VERSION",
        "kind": "constant",
        "signature": "AGI_STUDIO_DIRECTORY_HANDLE_VERSION",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_DIRECTORY_HANDLE_SLOTS",
        "kind": "constant",
        "signature": "AGI_STUDIO_DIRECTORY_HANDLE_SLOTS",
        "summary": ""
      },
      {
        "name": "AGI_STUDIO_HANDLE_LIMITS",
        "kind": "constant",
        "signature": "AGI_STUDIO_HANDLE_LIMITS",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "studio/main",
    "path": "agi/reference/studio/main.md",
    "source": "agi/studio/main.js",
    "import": "/agi/studio/main.js",
    "sourceHash": "8236490f255c697417ba677755c07b3067c1999db3f8788477c3f705d5ee58b3",
    "summary": "",
    "exports": []
  },
  {
    "schemaVersion": 1,
    "title": "studio/ui/LoadingProgress",
    "path": "agi/reference/studio/ui/LoadingProgress.md",
    "source": "agi/studio/ui/LoadingProgress.js",
    "import": "/agi/studio/ui/LoadingProgress.js",
    "sourceHash": "ef1c4411a2e8cff7daee838096e98e64aeb42f80b83c01a549ddd5f3470f5dcb",
    "summary": "Loading Progress UI - Shows what's loading and progress",
    "exports": [
      {
        "name": "LoadingProgress",
        "kind": "class",
        "signature": "class LoadingProgress",
        "summary": "Loading Progress UI - Shows what's loading and progress"
      },
      {
        "name": "loadingProgress",
        "kind": "constant",
        "signature": "loadingProgress",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/ComputeGraph",
    "path": "agi/reference/tensor/ComputeGraph.md",
    "source": "agi/tensor/ComputeGraph.js",
    "import": "/agi/tensor/ComputeGraph.js",
    "sourceHash": "26ca9b7e87d7e73cf8f8a75c9dcc10ae3f8b839e09d4eadead6913b9e81649ba",
    "summary": "Compute Graph Manages operation chaining and execution order for tensor operations",
    "exports": [
      {
        "name": "ComputeGraph",
        "kind": "class",
        "signature": "class ComputeGraph",
        "summary": "Compute Graph Manages operation chaining and execution order for tensor operations"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/GradientTape",
    "path": "agi/reference/tensor/GradientTape.md",
    "source": "agi/tensor/GradientTape.js",
    "import": "/agi/tensor/GradientTape.js",
    "sourceHash": "e96aeba07c60f4fbcfe8075ce345aa4b5fd8b38f4763454e74e31ad16969615b",
    "summary": "Gradient Tape - Automatic Differentiation Records operations for backward pass computation",
    "exports": [
      {
        "name": "GradientTape",
        "kind": "class",
        "signature": "class GradientTape",
        "summary": "Gradient Tape - Automatic Differentiation Records operations for backward pass computation"
      },
      {
        "name": "GradientContext",
        "kind": "class",
        "signature": "class GradientContext",
        "summary": "Context manager for gradient tape"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/Tensor",
    "path": "agi/reference/tensor/Tensor.md",
    "source": "agi/tensor/Tensor.js",
    "import": "/agi/tensor/Tensor.js",
    "sourceHash": "684fca9422ac801e2437832d8ddab6095c08198f2a3a826cae4b9686a1ffd61e",
    "summary": "GPU-resident float32 tensor with explicit readback and ownership.",
    "exports": [
      {
        "name": "Tensor",
        "kind": "class",
        "signature": "class Tensor",
        "summary": "GPU-resident float32 tensor with explicit readback and ownership."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/TensorCache",
    "path": "agi/reference/tensor/TensorCache.md",
    "source": "agi/tensor/TensorCache.js",
    "import": "/agi/tensor/TensorCache.js",
    "sourceHash": "0d8979cee9446b90b1e9f552b93dc7e6c641a12ccfdabc01ef644285169a3751",
    "summary": "Tensor Cache - Memory Management for GPU Tensors Manages tensor buffer lifecycle and memory reuse",
    "exports": [
      {
        "name": "TensorCache",
        "kind": "class",
        "signature": "class TensorCache",
        "summary": "Tensor Cache - Memory Management for GPU Tensors Manages tensor buffer lifecycle and memory reuse"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/TensorOps",
    "path": "agi/reference/tensor/TensorOps.md",
    "source": "agi/tensor/TensorOps.js",
    "import": "/agi/tensor/TensorOps.js",
    "sourceHash": "f92f4df6708803a2fae8c8e333bfbc0a61b1a26b2f037a82bfccd69dc485a11e",
    "summary": "GPU-resident float32 elementwise and reduction operations.",
    "exports": [
      {
        "name": "TensorOps",
        "kind": "class",
        "signature": "class TensorOps",
        "summary": "GPU-resident float32 elementwise and reduction operations."
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/TensorOpsAdvanced",
    "path": "agi/reference/tensor/TensorOpsAdvanced.md",
    "source": "agi/tensor/TensorOpsAdvanced.js",
    "import": "/agi/tensor/TensorOpsAdvanced.js",
    "sourceHash": "4646764c86d6e498d4a5b192df9b5d4c7df7fe8435b542b4c6ef9113a459549e",
    "summary": "",
    "exports": [
      {
        "name": "TensorOpsAdvanced",
        "kind": "class",
        "signature": "class TensorOpsAdvanced",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/shaders/activation",
    "path": "agi/reference/tensor/shaders/activation.md",
    "source": "agi/tensor/shaders/activation.js",
    "import": "/agi/tensor/shaders/activation.js",
    "sourceHash": "2c123cb5af1d1adc7bf8699ef1dbe4d8c5af04ae5cbdc7d740a81aad92d86c6c",
    "summary": "Activation Function Compute Shaders ReLU, Tanh, Sigmoid, and their derivatives",
    "exports": [
      {
        "name": "createActivationShader",
        "kind": "function",
        "signature": "createActivationShader(activationType = 'relu', workgroupSize = 256)",
        "summary": "Activation Function Compute Shaders ReLU, Tanh, Sigmoid, and their derivatives"
      },
      {
        "name": "createActivationDerivativeShader",
        "kind": "function",
        "signature": "createActivationDerivativeShader(activationType = 'relu', workgroupSize = 256)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/shaders/matmul",
    "path": "agi/reference/tensor/shaders/matmul.md",
    "source": "agi/tensor/shaders/matmul.js",
    "import": "/agi/tensor/shaders/matmul.js",
    "sourceHash": "0591fa80ce07d12cc1a7dcfe7f6a6e9ddc65b3f1987e43c664238859bb2900b7",
    "summary": "Matrix Multiplication Compute Shader C = A * B where A is [M x K] and B is [K x N]",
    "exports": [
      {
        "name": "createMatmulShader",
        "kind": "function",
        "signature": "createMatmulShader(workgroupSize = 8)",
        "summary": "Matrix Multiplication Compute Shader C = A * B where A is [M x K] and B is [K x N]"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "tensor/shaders/reduction",
    "path": "agi/reference/tensor/shaders/reduction.md",
    "source": "agi/tensor/shaders/reduction.js",
    "import": "/agi/tensor/shaders/reduction.js",
    "sourceHash": "7f3ada19ad8db6a3d564df74178aa7cb8491dd3a7456547dee5e5c864b2bc668",
    "summary": "Reduction Operations Compute Shaders Sum, Max, Min reductions along axes",
    "exports": [
      {
        "name": "createSumReductionShader",
        "kind": "function",
        "signature": "createSumReductionShader(workgroupSize = 256)",
        "summary": "Reduction Operations Compute Shaders Sum, Max, Min reductions along axes"
      },
      {
        "name": "createMaxReductionShader",
        "kind": "function",
        "signature": "createMaxReductionShader(workgroupSize = 256)",
        "summary": ""
      },
      {
        "name": "createMeanReductionShader",
        "kind": "function",
        "signature": "createMeanReductionShader(workgroupSize = 256)",
        "summary": ""
      },
      {
        "name": "createSoftmaxShader",
        "kind": "function",
        "signature": "createSoftmaxShader(workgroupSize = 256)",
        "summary": ""
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "ui/MetricsDisplay",
    "path": "agi/reference/ui/MetricsDisplay.md",
    "source": "agi/ui/MetricsDisplay.js",
    "import": "/agi/ui/MetricsDisplay.js",
    "sourceHash": "bd57b45b09f3d3fb0e7aa3f6661bd2c4dc974362c16de34c4af133b912bd41fb",
    "summary": "Metrics Display Real-time training statistics display",
    "exports": [
      {
        "name": "MetricsDisplay",
        "kind": "class",
        "signature": "class MetricsDisplay",
        "summary": "Metrics Display Real-time training statistics display"
      }
    ]
  },
  {
    "schemaVersion": 1,
    "title": "ui/TrainingUI",
    "path": "agi/reference/ui/TrainingUI.md",
    "source": "agi/ui/TrainingUI.js",
    "import": "/agi/ui/TrainingUI.js",
    "sourceHash": "b00b6b3bf2d60e010b022348ae2ec48b7a9b543d2605278c23c4d44bf93eed64",
    "summary": "Training UI Main control panel for training interface",
    "exports": [
      {
        "name": "TrainingUI",
        "kind": "class",
        "signature": "class TrainingUI",
        "summary": "Training UI Main control panel for training interface"
      }
    ]
  }
]