{"_id":"@aituber-onair/noise","_rev":"4-83b0fcae70edd57694f0db2f1e00d767","name":"@aituber-onair/noise","dist-tags":{"latest":"0.0.4"},"versions":{"0.0.1":{"name":"@aituber-onair/noise","version":"0.0.1","keywords":["aituber","vtuber","llm","response-rewrite","noise","browser"],"author":{"url":"https://github.com/shinshin86","name":"shinshin86"},"license":"MIT","_id":"@aituber-onair/noise@0.0.1","maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"homepage":"https://github.com/shinshin86/aituber-onair#readme","bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"dist":{"shasum":"2c256a0f7b690b3247e895c90232f325623090f0","tarball":"https://registry.npmjs.org/@aituber-onair/noise/-/noise-0.0.1.tgz","fileCount":75,"integrity":"sha512-cokqHWDNbcABndV+pTnG/hpyVvifQMDPrL0bTEgRAzoHciSbTGveAlx24Cuk/MRSi4fc0N83PFrr+l6PC7OS9Q==","signatures":[{"sig":"MEYCIQDj/aMJpYiOp9HKNvVBayz1oSgdO4WoNYccxWaIPBhlYAIhAOHrL2EaJ8Ym3vBwmEg+1sZvcuIMDDgEXEET4DKZZx5i","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":155335},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./web":{"types":"./dist/web.d.ts","import":"./dist/web.js"},"./node":{"types":"./dist/node.d.ts","import":"./dist/node.js"}},"gitHead":"05d6675ea9cf22f6ffe58bcf2965d8ff28af1480","scripts":{"fmt":"biome format . --write","lint":"biome lint .","test":"npm run typecheck && vitest run","build":"npm run clean && tsc --project tsconfig.json","clean":"rm -rf dist","fmt:check":"biome format .","typecheck":"tsc --noEmit --project tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","example:noise-sample":"vite --host 127.0.0.1 examples/noise-sample","example:noise-sample:build":"vite build examples/noise-sample"},"_npmUser":{"name":"shinshin86","email":"shinshin86npm@gmail.com"},"repository":{"url":"git+https://github.com/shinshin86/aituber-onair.git","type":"git","directory":"packages/noise"},"_npmVersion":"10.8.2","description":"A context-aware response noise engine for AI VTubers.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","browserslist":["defaults","not IE 11"],"dependencies":{"@aituber-onair/chat":"^0.37.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.4.21","jsdom":"^22.1.0","vitest":"^1.3.1","typescript":"^5.0.0","@types/node":"^18.15.0","@biomejs/biome":"1.9.4","@vitest/coverage-v8":"^1.3.1"},"_npmOperationalInternal":{"tmp":"tmp/noise_0.0.1_1781057419117_0.0535390479224358","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@aituber-onair/noise","version":"0.0.2","keywords":["aituber","vtuber","llm","response-rewrite","noise","browser"],"author":{"url":"https://github.com/shinshin86","name":"shinshin86"},"license":"MIT","_id":"@aituber-onair/noise@0.0.2","maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"homepage":"https://github.com/shinshin86/aituber-onair#readme","bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"dist":{"shasum":"97ed0e08cb915549a9b38b95b56bda8ad9a4f8c3","tarball":"https://registry.npmjs.org/@aituber-onair/noise/-/noise-0.0.2.tgz","fileCount":90,"integrity":"sha512-VRN1SInr52TTaJjKIU081/nk3Amrt+D3H6qSbqdYjeDtirBi7i2IcUhdQRftzBtoNhtNt89kvkX97boUDno5Pg==","signatures":[{"sig":"MEYCIQDnRtkPWN3uTo8jpCz8JoLapDixvu0sB8y7+I1a/D32wgIhALriwP2s+ZxmN9othvGXjEPnjIRvb65Dhr1jk9LJyyBa","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":247450},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./web":{"types":"./dist/web.d.ts","import":"./dist/web.js"},"./node":{"types":"./dist/node.d.ts","import":"./dist/node.js"}},"gitHead":"61e14f0335b8d89fcffd6ac294223ae748a91a72","scripts":{"fmt":"biome format . --write","lint":"biome lint .","test":"npm run typecheck && vitest run","build":"npm run clean && tsc --project tsconfig.json","clean":"rm -rf dist","fmt:check":"biome format .","typecheck":"tsc --noEmit --project tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","example:noise-sample":"vite --host 127.0.0.1 examples/noise-sample","example:noise-sample:build":"vite build examples/noise-sample","example:noise-session-sample":"vite --host 127.0.0.1 examples/noise-session-sample","example:noise-session-sample:build":"vite build examples/noise-session-sample"},"_npmUser":{"name":"shinshin86","email":"shinshin86npm@gmail.com"},"repository":{"url":"git+https://github.com/shinshin86/aituber-onair.git","type":"git","directory":"packages/noise"},"_npmVersion":"10.8.2","description":"A context-aware response noise engine for AI VTubers.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","browserslist":["defaults","not IE 11"],"dependencies":{"@aituber-onair/chat":"^0.40.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.4.21","jsdom":"^22.1.0","vitest":"^1.3.1","typescript":"^5.0.0","@types/node":"^18.15.0","@biomejs/biome":"1.9.4","@vitest/coverage-v8":"^1.3.1"},"_npmOperationalInternal":{"tmp":"tmp/noise_0.0.2_1781746866838_0.2721940193446586","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@aituber-onair/noise","version":"0.0.3","keywords":["aituber","vtuber","llm","response-rewrite","noise","browser"],"author":{"url":"https://github.com/shinshin86","name":"shinshin86"},"license":"MIT","_id":"@aituber-onair/noise@0.0.3","maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"homepage":"https://github.com/shinshin86/aituber-onair#readme","bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"dist":{"shasum":"8083f6062eaacee6f2c4cad1f81665d8acf1767f","tarball":"https://registry.npmjs.org/@aituber-onair/noise/-/noise-0.0.3.tgz","fileCount":190,"integrity":"sha512-iJKC7DFHLfHXHCb7fHLMXRskNkLx1mmxU8AQLrWl81YNdPs1kzZ1sras+wKSJ/Qydnm2GCkEz3EF9aL170DhBA==","signatures":[{"sig":"MEYCIQDv+9bXi1q/57nJRs6uggSCl4XmyUKuQMeEOQItghNH9gIhALYLStUypy3oyAdY9+xlNWICy6EBRSQU9qzUSVS5ZTtF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":535300},"main":"dist/cjs/index.js","type":"module","types":"dist/esm/index.d.ts","module":"dist/esm/index.js","exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./web":{"import":{"types":"./dist/esm/web.d.ts","default":"./dist/esm/web.js"},"require":{"types":"./dist/cjs/web.d.ts","default":"./dist/cjs/web.js"}},"./node":{"import":{"types":"./dist/esm/node.d.ts","default":"./dist/esm/node.js"},"require":{"types":"./dist/cjs/node.d.ts","default":"./dist/cjs/node.js"}}},"gitHead":"507f276e22114d43a314bd35e490438cec47b565","scripts":{"fmt":"biome format . --write","lint":"biome lint .","test":"npm run typecheck && vitest run","build":"npm run clean && npm run build:esm && npm run build:cjs","clean":"rm -rf dist","build:cjs":"tsc --project tsconfig.cjs.json && node -e \"require('node:fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }))\"","build:esm":"tsc --project tsconfig.esm.json","fmt:check":"biome format .","typecheck":"tsc --noEmit --project tsconfig.json","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","example:noise-sample":"vite --host 127.0.0.1 examples/noise-sample","example:noise-sample:build":"vite build examples/noise-sample","example:noise-session-sample":"vite --host 127.0.0.1 examples/noise-session-sample","example:noise-session-sample:build":"vite build examples/noise-session-sample"},"_npmUser":{"name":"shinshin86","email":"shinshin86npm@gmail.com"},"repository":{"url":"git+https://github.com/shinshin86/aituber-onair.git","type":"git","directory":"packages/noise"},"_npmVersion":"10.8.2","description":"A context-aware response noise engine for AI VTubers.","directories":{},"sideEffects":false,"_nodeVersion":"20.20.2","browserslist":["defaults","not IE 11"],"dependencies":{"@aituber-onair/chat":"^0.46.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.4.21","jsdom":"^22.1.0","vitest":"^1.3.1","typescript":"^5.0.0","@types/node":"^18.15.0","@biomejs/biome":"1.9.4","@vitest/coverage-v8":"^1.3.1"},"_npmOperationalInternal":{"tmp":"tmp/noise_0.0.3_1783930780869_0.092149717561953","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"_id":"@aituber-onair/noise@0.0.4","bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"dist":{"shasum":"d14eee4b2326bd5495b0011d1b347984954ccad3","tarball":"https://registry.npmjs.org/@aituber-onair/noise/-/noise-0.0.4.tgz","fileCount":262,"integrity":"sha512-AJOBtmd6/t3Z8JysXOk104jMRINxnxS2TLis+de16URgN1MvQVLW6ilmpKZ+EImST00lPNzLgmDZS5HqZDF1Mg==","signatures":[{"sig":"MEUCIFbshXW0ygcZUgUqseEwVBIkpz2zlN7fW7bp1zQjbmbdAiEAplOZmvCoMapJLwHz001m40tpPrzJV6C1r+lY+UYrW6Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDYXIDin94LPEh/GPICZ7iB3wLGa7kJoCBMD4kkIyek7AiEAqHv3ZSLQVGaqGtXDfpaIGc4FhAQ1+UsGJDQ6jaqGxaY="}],"unpackedSize":919685},"main":"dist/cjs/index.js","name":"@aituber-onair/noise","type":"module","types":"dist/esm/index.d.ts","author":{"url":"https://github.com/shinshin86","name":"shinshin86"},"module":"dist/esm/index.js","exports":{".":{"import":{"types":"./dist/esm/index.d.ts","default":"./dist/esm/index.js"},"require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./web":{"import":{"types":"./dist/esm/web.d.ts","default":"./dist/esm/web.js"},"require":{"types":"./dist/cjs/web.d.ts","default":"./dist/cjs/web.js"}},"./node":{"import":{"types":"./dist/esm/node.d.ts","default":"./dist/esm/node.js"},"require":{"types":"./dist/cjs/node.d.ts","default":"./dist/cjs/node.js"}}},"gitHead":"0bf72faf192e3fb4f4968a1bf3eec09e3a1de31c","license":"MIT","scripts":{"fmt":"biome format . --write","lint":"biome lint .","test":"npm run typecheck && vitest run","build":"npm run clean && npm run build:esm && npm run build:cjs","clean":"rm -rf dist","build:cjs":"tsc --project tsconfig.cjs.json && node -e \"require('node:fs').writeFileSync('dist/cjs/package.json', JSON.stringify({ type: 'commonjs' }))\"","build:esm":"tsc --project tsconfig.esm.json","fmt:check":"biome format .","typecheck":"tsc --noEmit --project tsconfig.json","test:watch":"vitest","example:brain":"npm run build && node scripts/brain-example.mjs","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build","malecns:prepare":"node scripts/prepare-malecns.mjs","example:brain-chat":"vite --config examples/noise-brain-chat/vite.config.ts","example:noise-sample":"vite --host 127.0.0.1 examples/noise-sample","example:neural-rewrite":"npm run build && node examples/neural-rewrite-cli/run.mjs","example:brain-chat:build":"tsc --project examples/noise-brain-chat/tsconfig.json --noEmit && vite build --config examples/noise-brain-chat/vite.config.ts","example:noise-sample:build":"vite build examples/noise-sample","example:noise-session-sample":"vite --host 127.0.0.1 examples/noise-session-sample","example:noise-session-sample:build":"vite build examples/noise-session-sample"},"version":"0.0.4","_npmUser":{"name":"shinshin86","email":"shinshin86npm@gmail.com"},"homepage":"https://github.com/shinshin86/aituber-onair#readme","keywords":["aituber","vtuber","llm","response-rewrite","noise","browser"],"repository":{"url":"git+https://github.com/shinshin86/aituber-onair.git","type":"git","directory":"packages/noise"},"_npmVersion":"10.8.2","description":"A context-aware response noise engine for AI VTubers.","directories":{},"maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"sideEffects":false,"_nodeVersion":"20.20.2","browserslist":["defaults","not IE 11"],"dependencies":{"@aituber-onair/chat":"^0.55.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^5.4.21","jsdom":"^22.1.0","lz4js":"^0.2.0","vitest":"^1.3.1","typescript":"^5.0.0","@types/node":"^18.15.0","apache-arrow":"^21.2.0","@biomejs/biome":"1.9.4","@vitest/coverage-v8":"^1.3.1","@aituber-onair/kizuna":"^0.0.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/noise_0.0.4_1789629253018_0.7383684873661658"}}},"time":{"created":"2026-06-10T02:10:18.984Z","modified":"2026-09-17T07:14:13.306Z","0.0.1":"2026-06-10T02:10:19.258Z","0.0.2":"2026-06-18T01:41:07.005Z","0.0.3":"2026-07-13T08:19:41.037Z","0.0.4":"2026-09-17T07:14:13.109Z"},"bugs":{"url":"https://github.com/shinshin86/aituber-onair/issues"},"author":{"url":"https://github.com/shinshin86","name":"shinshin86"},"license":"MIT","homepage":"https://github.com/shinshin86/aituber-onair#readme","keywords":["aituber","vtuber","llm","response-rewrite","noise","browser"],"repository":{"url":"git+https://github.com/shinshin86/aituber-onair.git","type":"git","directory":"packages/noise"},"description":"A context-aware response noise engine for AI VTubers.","maintainers":[{"name":"shinshin86","email":"shinshin86npm@gmail.com"}],"readme":"# @aituber-onair/noise\n\n![@aituber-onair/noise logo](https://raw.githubusercontent.com/shinshin86/aituber-onair/main/packages/noise/images/aituber-onair-noise.png)\n\nAITuber OnAir Noise is a context-aware response rewrite engine for disturbing\npredictable LLM phrasing without changing the meaning of the reply.\n\nThe optional neural response adapter also permits changes in meaning, while\nkeeping a recognizable character and a coherent conversation.\nFor real wiring, follow the [dataset setup guide](#malecns-setup).\n\nDo not let AI responses end in predictable harmony.\n\nIt is designed for AI VTubers and AI character streams where a response can feel\ntoo clean, too agreeable, or too neatly summarized. The package detects\npredictability, builds structured friction parameters, asks an LLM for multiple\nrewrite candidates, and selects the candidate that best preserves the character\nwhile avoiding a predictable landing.\n\nNoise is not just a rewrite engine: it is a deviation orchestration engine.\nResearch across conversation analysis, improv theory, humor theory, and field\nanalysis of successful AI VTubers converges on one formula (see\n`docs/design-research.md`):\n\n> Pleasant unpredictability = (established pattern) x (deviation shipped with a\n> simultaneous \"this is play\" marker) x (safe target) x (relational license) x\n> (return to pattern). Remove any factor and the same output flips from charm\n> to malfunction.\n\nSo in addition to rewriting, Noise schedules when deviation is allowed\n(rhythm), decides how much deviation the relationship has earned\n(relationship capital), refuses to disturb sincere moments (sincerity gate),\ncertifies teasing as play (play markers), reuses shared memories as running\ngags (gag ledger), and learns from audience reactions (reaction loop).\n\n## Why this exists\n\nLLMs are trained on the average of a huge amount of text, and preference tuning\n(RLHF) pushes them further toward replies that are safe, agreeable, and neatly\nsummarized. That is fine for an assistant, but for an AI character stream it\nproduces **predictable harmony** (予定調和): the same temperature every time, a\ntidy closing every time, and an audience that gets bored. Human conversation is\nengaging precisely because it does *not* go to plan — a retort, a pause, a\ndeliberately withheld reaction, a callback to an old joke.\n\nThe hard part is **not** generating disruption — an LLM can do that. The hard\npart is that whether a broken expectation reads as *charm* or as *malfunction*\ndoes not live in the text; it lives in the receiver. The same blunt line is\n\"endearing gap\" from a beloved regular character and \"rude\" from a stranger.\nSo Noise is less a text generator and more a controller: it manages **when, how\nfar, and toward whom** a reply may deviate, and learns from how the audience\nreacts.\n\n## How it works (one turn)\n\nAfter the LLM produces a draft reply, Noise runs this pipeline (the same one the\nbrowser sample visualizes under \"ノイズの判断を見る\"):\n\n1. **Diagnose** — is this draft too predictable? Detect clean closings,\n   over-apology, over-agreement, etc., and score it.\n2. **Three gates — may we disrupt at all?**\n   - **Sincerity gate**: if the viewer is making a serious or vulnerable bid,\n     stop everything (failed uptake of a sincere moment is the worst violation).\n   - **Relationship capital**: unlock stronger interventions (teasing, callbacks)\n     only as the bond grows.\n   - **Rhythm**: rest right after a disruption, because constant disruption\n     becomes a new predictable style.\n3. **Plan** — choose which interventions to use, limited to what the gates allow.\n4. **Generate & score candidates** — ask the LLM for several rewrites and score\n   them on predictability reduction, character preservation, genericity, and\n   whether a play marker is present.\n5. **Select & quality-check** — pick the strongest safe candidate; reject\n   over-corrections.\n6. **Learn** — record what was actually applied; later `reportReaction()` feeds\n   the audience response back, raising or lowering how far Noise will push next\n   time and promoting well-received moments into the gag ledger.\n\nIn short: **keep the character's \"form\", choreograph when and how far to break\nit, and always return to form.** The laughter-flavored reaction signals are not\nthere to make the AI tell jokes — they are the sensor that measures whether a\ndeviation (a bet) actually paid off.\n\n## Basic Usage\n\n```ts\nimport { createContaminator } from '@aituber-onair/noise';\n\nconst contaminator = createContaminator({\n  intensity: 0.42,\n  mode: 'performer',\n  chat: {\n    provider: 'openai',\n    options: {\n      apiKey: process.env.OPENAI_API_KEY!,\n      model: 'gpt-4o-mini',\n    },\n  },\n});\n\nconst result = await contaminator.contaminate({\n  systemPrompt: 'You are a strange AI VTuber.',\n  messages: [{ role: 'user', content: 'Thanks for the stream!' }],\n  draft:\n    'Thank you for coming today. It was a very fun stream. Please look forward to the next one.',\n  streamContext: {\n    currentSituation: 'The stream is ending too neatly.',\n  },\n  constraints: {\n    preserveCodeBlocks: true,\n    preserveUrls: true,\n    preserveNumbers: true,\n    maxAddedChars: 120,\n  },\n});\n\nconsole.log(result.text);\nconsole.log(result.diagnosis);\nconsole.log(result.plan);\nconsole.log(result.applied);\nconsole.log(result.quality);\n```\n\n## Conditional Usage\n\nNoise does not have to run on every LLM reply. In a production stream, a common\npattern is to diagnose the draft first, then rewrite only when the response is\nlikely to land too safely:\n\n```ts\nimport {\n  createContextFingerprint,\n  createContaminator,\n  diagnosePredictability,\n} from '@aituber-onair/noise';\n\nconst context = createContextFingerprint({\n  systemPrompt,\n  messages,\n  streamContext,\n});\nconst diagnosis = diagnosePredictability({\n  draft: llmReply,\n  context,\n});\nconst shouldUseNoise = diagnosis.score >= 0.45;\n\nconst finalReply = shouldUseNoise\n  ? (\n      await contaminator.contaminate({\n        systemPrompt,\n        messages,\n        draft: llmReply,\n        streamContext,\n      })\n    ).text\n  : llmReply;\n```\n\nThis makes Noise behave like a post-generation effect: use it for overly safe\nclosings, repeated phrasing, forced positivity, and stream situations where a\nflat response would weaken the character. Skip it for precise announcements,\nsystem messages, and high-stakes text.\n\n## Browser Example\n\nThis package includes a browser lab for trying LLM-based rewrites and adaptive\nmemory providers.\n\n```sh\nnpm -w @aituber-onair/noise run example:noise-sample\n```\n\n## Deviation Orchestration\n\n### Rhythm: platform -> tilt -> platform\n\nA deviation only reads as an event against a stretch of normal, in-character\nturns. The built-in rhythm controller skips noise right after a tilt\n(cooldown) and can require platform turns before tilting:\n\n```ts\nconst contaminator = createContaminator({\n  rhythm: {\n    minPlatformTurns: 2, // in-character turns required before a tilt\n    cooldownTurns: 2, // in-character turns enforced after a tilt\n    tiltThreshold: 0.45, // diagnosis score needed to tilt\n    forcedTiltAfter: 8, // tilt anyway after this many flat turns\n  },\n});\n```\n\nBy default `tiltThreshold` is `0.35`, so drafts that already land naturally\nare left untouched out of the box; set it to `0` to make every turn eligible.\n\nWhen a turn is skipped, `contaminate()` returns the draft unchanged with\n`result.skipped` describing why (`'cooldown'`, `'platform'`,\n`'low_predictability'`, `'repair'`, `'sincerity'`,\n`'no_licensed_intervention'`, `'model_error'`, or `'quality_fail'`). Pass\n`forceTilt: true` in the input to bypass the rhythm gate.\n\n### Relationship capital\n\nThe same tease that charms an established audience alienates a new one. Pass\n`relationshipCapital` (0-1) per call — derived from any bond system, for\nexample kizuna points — and Noise caps both the effective mode and the\nintervention vocabulary:\n\n- `stranger` (< 0.25): phrasing-level edits only (`subtle`).\n- `acquaintance` (< 0.55): + soft disagreement, dispreferred shape, length\n  violation (`performer`).\n- `regular` (< 0.8): + contrarian reframe, callbacks, boke bait, status\n  seesaw (`inversion`).\n- `companion` (>= 0.8): + tsukkomi, withheld uptake (`chaotic`).\n\n```ts\nconst result = await contaminator.contaminate({\n  systemPrompt,\n  messages,\n  draft,\n  relationshipCapital: 0.7,\n});\nconsole.log(result.gates.relationship.tier); // 'regular'\n```\n\nWith `@aituber-onair/kizuna` the mapping is one line — normalize the user's\npoints into 0-1:\n\n```ts\nconst user = await kizuna.getUser(userId);\nconst relationshipCapital = Math.min(1, (user?.points ?? 0) / 1000);\n```\n\n### Sincerity gate\n\nWhen recent user messages carry a sincere bid — distress, a serious\nconsultation, a heavy life event — all noise is suppressed before any other\nprocessing. Failed uptake of a sincere moment is the worst possible violation.\nDisable with `sincerityGate: false` if the app handles this elsewhere.\n\n### Play markers\n\nBenign violation theory: a violation must be decoded as play at the same\nmoment it lands. Teasing-class interventions (`tsukkomi`, `withheld_uptake`,\n`boke_bait`, `status_seesaw`, `contrarian_reframe`) require a playful marker\n(laughter token, exaggeration, self-tease) in the same reply; candidates\nwithout one are penalized and flagged with a `missing_play_marker` issue.\n\n### Gag ledger and callbacks\n\nCallbacks — resurfacing a shared past moment — are the highest-value,\nlowest-risk surprise: they are unexpected and prove memory at the same time.\n\n```ts\nawait contaminator.recordMoment({\n  summary: 'The viewer exploded a pudding in the fridge',\n  source: 'user',\n});\n// Later turns may plan a `callback` intervention with that moment as material.\n```\n\nMoments are also promoted automatically when a tilt gets a positive reaction.\n\n### Reaction loop\n\nEvery deviation is a bet; feed the observed result back:\n\n```ts\nconst reaction = await contaminator.reportReaction({ signal: 'laughter' });\n// 'laughter' | 'positive' | 'neutral' | 'silence' | 'pushback' | 'discomfort'\n```\n\nIn a live stream the reaction is directly observable in chat, so you can infer\nthe signal instead of hand-labelling it. Pass the output's `turnId` back so a\nlate reaction can only promote the tilt it belongs to:\n\n```ts\nimport { inferReactionFromComments } from '@aituber-onair/noise';\n\nconst output = await contaminator.contaminate({ ... });\n// ...collect the comments that arrived in the next few seconds...\nawait contaminator.reportReaction({\n  ...inferReactionFromComments(commentsAfterTilt),\n  turnId: output.turnId,\n});\n```\n\nPositive signals widen the violation budget and promote the latest tilt into\nthe gag ledger. Negative signals shrink the budget and schedule repair turns\nduring which noise stays off. Subscribe to lifecycle events via\n`onNoiseEvent` (`tilt_applied`, `noise_skipped`, `repair_advised`,\n`moment_recorded`, `callback_used`) to let the app stage reactions — solo AI\nchaos is nonsense, chaos with a visible reactor is comedy.\n\n### Positioning: why the vocabulary sounds like comedy\n\nNoise is **not** a library for making an AI character do comedy. The goal is\nunchanged: keep LLM replies from converging to the safe, average landing. The\ncomedy-flavored vocabulary (reactions like \"it got laughs\", boke/tsukkomi\ninterventions, the gag ledger) exists for three structural reasons:\n\n1. **Every deviation is a bet, and the payoff lives in the audience.**\n   Whether a broken expectation reads as charm or as malfunction is not a\n   property of the text — expectancy violations theory shows it is decided by\n   the receiver's appraisal. An engine that injects deviation without\n   observing reception is an open-loop controller: it cannot know whether to\n   push further or pull back. `reportReaction()` is that sensor, and the\n   violation budget is the feedback loop. The API signals themselves are\n   neutral (`laughter` / `positive` / `silence` / `pushback` / `discomfort`).\n2. **Humor research is borrowed as measurement science, not as a goal.**\n   The most developed body of knowledge about when a norm violation lands as\n   *pleasure* instead of offense is humor theory (benign violation theory,\n   boke/tsukkomi as a grammar for certifying deviation as play). Noise uses\n   it to keep deviations safe, the same way it uses conversation analysis for\n   response shapes — neither makes the output a joke.\n3. **In a live stream, laughter is the most observable proxy for \"the\n   deviation was accepted.\"** You cannot directly measure \"the audience\n   appraised the violation positively\", but you can literally count 草 and w\n   in chat. That is why the browser lab labels its reaction buttons in\n   streamer terms (ウケた / スベった): it is the sample's translation into\n   its own context, not the library's purpose. Likewise the gag ledger is at\n   heart a *shared-memory callback* device — resurfacing a moment the\n   audience lived through proves memory and deepens the relationship; being\n   funny is optional.\n\n## Rewrite Modes\n\n`mode` controls how far Noise may move the response away from a predictable\nlanding:\n\n- `subtle`: small edits that remove obvious polish.\n- `performer`: character-safe live-stream phrasing.\n- `bold`: stronger streamer judgment and clearer live tension.\n- `inversion`: reverses the expected emotional landing while preserving facts.\n- `chaotic`: the largest coherent disruption, with self-repair and unfinished\n  edges.\n\n## Design\n\nNoise works after an LLM has already produced a draft. It is independent from\nconversation-loop detectors such as `@aituber-onair/manneri`: those tools can\nwatch the conversation flow before generation, while Noise watches the response\nlanding after generation.\n\nThe engine pipeline:\n\n- `createContextFingerprint()` reads the persona, recent messages, and optional\n  `streamContext`.\n- `diagnosePredictability()` classifies why the draft feels too safe, generic,\n  or over-polished.\n- `assessSincerity()`, `resolveRelationshipTier()`, and `decideRhythm()` gate\n  whether this turn may deviate at all, and how far.\n- `buildInterventionPlan()` and `buildFrictionParameters()` turn the diagnosis\n  into structured instructions such as grounding in recent comments, reducing\n  over-apology, adding streamer judgment, dispreferred response shape,\n  boke/tsukkomi moves, status seesaw, or a callback from the gag ledger.\n- `generateRewriteCandidates()` asks an LLM for multiple candidates from those\n  structured parameters, each with a self-reported typicality so selection can\n  prefer the distribution tail.\n- `evaluateRewriteCandidates()` checks predictability reduction, context\n  grounding, specificity, persona preservation, meaning preservation,\n  aggression risk, ungrounded detail risk, genericity (stock phrases and\n  near-repeats of the character's own recent outputs), play markers, and\n  whether the final sentence — the highest-value surprise position — actually\n  changed.\n- `selectBestCandidate()` returns the strongest safe candidate.\n\nThe full intervention vocabulary:\n\n| Intervention | What it does |\n| --- | --- |\n| `ground_in_recent_comment` | Reference something a viewer actually said |\n| `add_streamer_judgment` | Make a streamer-side decision |\n| `soft_disagreement` | Replace clean agreement with a warm reservation |\n| `contrarian_reframe` | Reverse the expected emotional landing |\n| `self_repair` | Live-speech self-correction mid-flow |\n| `unfinished_margin` | Leave the final thought slightly open |\n| `reduce_over_apology` | Drop service-style apology tone |\n| `reduce_over_agreement` | Weaken automatic acceptance |\n| `increase_specificity` | Add a concrete anchor |\n| `acknowledge_tension` | Name the visible trouble |\n| `break_clean_closing` | Avoid a tidy goodbye |\n| `callback` | Resurface a gag-ledger moment as a running gag |\n| `dispreferred_shape` | Human-shaped hedged/grudging (dis)agreement |\n| `boke_bait` | Plant a correctable absurdity inviting the audience retort |\n| `tsukkomi` | Sharp but clearly playful retort to the absurd part |\n| `withheld_uptake` | Deadpan past the expected reaction once |\n| `status_seesaw` | Brief confident stance, immediately self-mocked |\n| `response_length_violation` | Strikingly short reply where a paragraph was expected |\n\nNoise does not import or depend on Manneri. If an app has external knowledge\nabout the stream, pass it as plain `streamContext`; Noise treats it as ordinary\nruntime context, not as a package-specific integration.\n\nThis package does not depend on any LLM SDK. You can use the built-in\n`@aituber-onair/chat` integration for OpenAI, OpenAI-compatible, Gemini, Claude,\nOpenRouter, xAI, Kimi, DeepSeek, Mistral, and Gemini Nano providers:\n\n```ts\nconst contaminator = createContaminator({\n  chat: {\n    provider: 'claude',\n    options: {\n      apiKey: process.env.CLAUDE_API_KEY!,\n      model: 'claude-3-5-haiku-latest',\n    },\n  },\n});\n```\n\nYou can also use a custom adapter:\n\n```ts\nconst contaminator = createContaminator({\n  model: {\n    async generate({ system, prompt }) {\n      const response = await fetch('/api/rewrite', {\n        method: 'POST',\n        body: JSON.stringify({ system, prompt }),\n      });\n      const json = await response.json();\n      return json.text;\n    },\n  },\n});\n```\n\nIf none of `chat`, `llm`, or `model` is provided, `contaminate()` throws. Noise\nno longer falls back to local rule-based rewriting because that can change a\ncharacter's personality too easily.\n\n## Safety\n\nBy default, code blocks, URLs, and numbers are protected before rewriting and\nrestored after rewriting. The safety guard also avoids mutating high-stakes\nmedical, legal, and financial text.\n\nThe purpose of this package is not to make the AI more human or to break facts.\nIt only disturbs the way a reply lands when it is becoming too predictable.\n\n## Failure Handling\n\nNoise is a post-generation effect: losing a rewrite is acceptable on a live\nstream, losing the reply is not. `contaminate()` therefore never throws on\nrewrite-model failures — any model error returns the draft unchanged with\n`skipped.reason === 'model_error'`, and malformed/truncated candidate JSON\nfalls back to the draft instead of shipping raw model output. Two options\ntighten this further:\n\n```ts\nconst contaminator = createContaminator({\n  // Abort a hanging rewrite call and return the draft.\n  modelTimeoutMs: 4000,\n  // If every candidate fails the quality report, return the draft\n  // (skipped.reason === 'quality_fail') instead of the failing rewrite.\n  fallbackToDraftOnQualityFail: true,\n});\n```\n\nProtected spans (code blocks, URLs, numbers) are replaced with placeholder\ntokens before the rewrite; the model is instructed to keep them verbatim, and\nany candidate that drops or mangles one degrades to the draft.\n\n## Quality Report\n\nEvery rewrite returns a `quality` report:\n\n```ts\nif (!result.quality.passed) {\n  console.warn(result.quality.issues);\n}\n```\n\nThe report is intentionally conservative. It flags outputs that are still too\npredictable, too aggressive for the character, over-explain the noise, or add\ndetails that were not present in the draft or recent conversation.\n\n## Custom Lexicon\n\nThe built-in detection vocabulary only knows generic assistant phrasing. A\ncharacter's own catchphrases, habitual closings, and play-marker style are\napp-specific knowledge — pass them as a lexicon (case-insensitive substring\nmatching):\n\n```ts\nconst contaminator = createContaminator({\n  lexicon: {\n    // Counts as predictable wording during diagnosis.\n    predictablePhrases: ['それでは今日のまとめコーナー'],\n    // Counts as generic stock replies for the genericity penalty.\n    stockReplies: ['ナイスファイトです'],\n    // Accepted as \"this is play\" markers for teasing-class interventions.\n    playMarkers: ['にゃはは'],\n  },\n});\n```\n\nThe same option is accepted by the standalone `scorePredictability()`,\n`diagnosePredictability()`, `scoreGenericity()`, `hasPlayMarker()`, and\n`evaluateRewriteCandidates()` functions. The adaptive memory complements this\nat runtime: closings and phrases the character actually repeats are learned\nand fed back into the diagnosis automatically.\n\n## Adaptive Memory\n\nNoise can keep a small memory of predictable response patterns. The memory does\nnot store the full conversation by default. It tracks repeated closings,\nrepeated phrases, the character's own recent responses (for the genericity\npenalty), recently used rewrite directives, and topic-level loops so later\nplans can avoid collapsing into the same style of rewrite. It also persists the\ndeviation orchestration state: the rhythm counters, the violation budget\nlearned from reactions, and the gag ledger of memorable moments.\n\nWithout a configured store, the same state still works in-memory for the\nlifetime of the contaminator instance, so the rhythm controller and reaction\nloop function out of the box.\n\nThe root package exports an environment-independent in-memory store:\n\n```ts\nimport {\n  InMemoryNoiseMemoryStore,\n  createContaminator,\n} from '@aituber-onair/noise';\n\nconst store = new InMemoryNoiseMemoryStore();\n\nconst contaminator = createContaminator({\n  memory: {\n    scopeId: 'stream-session',\n    store,\n  },\n});\n```\n\nFor browsers, import the web provider:\n\n```ts\nimport { LocalStorageNoiseMemoryStore } from '@aituber-onair/noise/web';\n\nconst store = new LocalStorageNoiseMemoryStore();\n```\n\nFor Node.js, import the node provider:\n\n```ts\nimport { JsonFileNoiseMemoryStore } from '@aituber-onair/noise/node';\n\nconst store = new JsonFileNoiseMemoryStore({\n  filePath: './noise-memory.json',\n});\n```\n\n`detectNoiseRuntime()` can detect `browser`, `node`, or `unknown`, but the\nrecommended production style is to import `@aituber-onair/noise/web` or\n`@aituber-onair/noise/node` explicitly. This keeps browser bundles from pulling\nin Node.js modules.\n\nThe package ships dual ESM (`dist/esm`) and CommonJS (`dist/cjs`) builds, so\nboth `import` and `require` work in Node.js.\n\n## Streaming\n\n`createContaminationStream()` uses the Web-standard `TransformStream` API. The\ncurrent MVP buffers the full text and contaminates it on flush so the engine can\nrewrite with enough context.\n\n## Experimental neural modulation\n\n`createVirtualNoiseBrain()` generates a small deterministic reservoir locally.\nIt is the default backend when opting into the brain feature; omitting\n`modulator` keeps existing Noise behavior. There is no dataset download during\ninstallation or normal startup.\n\n```ts\nimport { createContaminator, createVirtualNoiseBrain } from '@aituber-onair/noise';\n\nconst brain = createVirtualNoiseBrain({ seed: 42 });\nconst contaminator = createContaminator({\n  model, // Your existing RewriteModel\n  modulator: brain,\n  fallbackToDraftOnQualityFail: true,\n});\n```\n\nThe virtual backend defaults to 1,024 neurons, 16 outgoing connections per\nneuron, and 24 simulation steps of 1 ms. It uses a seeded random graph with\n80% excitatory / 20% inhibitory source neurons. These are artificial design\nchoices, not a reconstruction of a fly. Its graph arrays occupy 143,380 bytes;\nstate arrays require additional memory. Optional `neurons`,\n`connectionsPerNeuron`, `steps`, and `dtMs` configure the experiment.\n\nBoth backends reset every turn. Six neutral stimulus channels encode existing\nenergy, tension, repetition, predictability, volatility, and viewer-intent\nsignals without another LLM call. A discrete leaky integrate-and-fire model\nuses a 20 ms leak constant, threshold 1, 2 ms refractory period, one-step\nsynaptic delay, gain 4, and pulses every four steps. Only spiking neurons visit\noutgoing edges; neuron state is scanned every step. There is no learning.\n\nReadout includes global activity, descending-population activity (an artificial\nreadout quarter in the virtual backend), side balance, dispersion, and four\nseeded signed projections of readout spike counts. Hand-designed mappings\nbias contrarian reframing, self-repair/unfinished margins, playful interventions,\nand persona volatility. Neither input nor output semantics are biological\nclaims. The fly does not understand language, praise, or insults.\n\nModulation runs only after sincerity/rhythm gates and an initial licensed plan.\nThe relationship allowlist still applies before biased selection. Intensity\nmultipliers are limited to 0.75–1.25, intervention biases to ±0.25, and persona\ndeltas to ±0.20; final values remain within 0–1. Non-finite controls become\nneutral, and rejected or malformed modulation falls back to the original plan.\nExisting protected-span and quality behavior remains intact. Set\n`fallbackToDraftOnQualityFail: true` to reject failed rewrites; modulation does\nnot override this existing application setting. `output.modulation` exposes\nbounded controls and a small activity summary, including on later model/quality\nfailures. The default asynchronous modulator deadline is 1,000 ms\n(`modulatorTimeoutMs`); a timer cannot interrupt synchronous CPU work.\n\n### Optional MaleCNS v1.0 backend\n\nThe MaleCNS connectome driven experimental reservoir uses the actual retained\nwiring graph. It is an approximate point-neuron simulation, not an accurate\ndigital reconstruction of a living fly. The graph is **not bundled in npm**.\n\n<a id=\"malecns-setup\"></a>\n\n#### Set up real wiring\n\nSkip this setup for the virtual circuit. For real wiring, download the official\nsource files and convert them with the repository's preparation script. The\nscript and CLI/WebUI examples are not included in the npm package; use a checkout\nof this GitHub repository.\n\n**1. Prepare the repository**\n\nRequires Node.js 20+, npm and Git. For a new checkout, run the following commands.\nFor an existing checkout, start at `npm ci` from its root directory.\n\n```sh\ngit clone https://github.com/shinshin86/aituber-onair.git\ncd aituber-onair\nnpm ci\nnpm -w @aituber-onair/chat run build\nnpm -w @aituber-onair/noise run build\n```\n\n**2. Download and convert**\n\nContinue from the repository root:\n\n```sh\nnpm -w @aituber-onair/noise run malecns:prepare -- \\\n  --source data/malecns-source --out data/malecns-v1 --download\n```\n\n`--download` fetches missing source files from the official MaleCNS Google Cloud\nStorage bucket, then converts them. Dataset files are not downloaded from this\nrepository, during npm installation, or when starting the virtual circuit.\n\nWorkspace command paths resolve from `packages/noise`. Sources are stored in\n`packages/noise/data/malecns-source`; converted data goes to\n`packages/noise/data/malecns-v1`. Wiring alone occupies approximately 207 MB;\nsource files, positions and annotations require additional disk space.\n\nIf the official source files are already in the `--source` directory, omit\n`--download`. The output directory must not exist. After a failed attempt, retry\nwith a different output such as `--out data/malecns-v1-retry`.\n\nSuccessful preparation prints a JSON summary and writes `manifest.json` last.\nThe output directory contains:\n\n| File | Purpose |\n| --- | --- |\n| `manifest.json` | Version, counts, hashes and conversion settings |\n| `graph.bin` | Wiring loaded by Noise |\n| `metadata.json` | Neuron annotations for the WebUI |\n| `soma-positions.f32` | Soma positions for the WebUI |\n| `ATTRIBUTION.txt` | Source credits, license name and modification notice |\n\n**3. Check loading**\n\nThis command loads the real graph and runs the circuit without an LLM call.\nIts text output is a plumbing check, not a language-quality comparison.\n\n```sh\nMALECNS_DATA_DIR=./packages/noise/data/malecns-v1 \\\n  node packages/noise/scripts/brain-example.mjs\n```\n\nFor this direct `node` command, paths resolve from the repository root. Without\n`MALECNS_DATA_DIR`, the script uses the virtual circuit.\n\n**4. Use the WebUI or CLI**\n\n```sh\nnpm -w @aituber-onair/noise run example:brain-chat\n```\n\nOpen `http://127.0.0.1:5183`, open **設定**, and select\n**ハエの脳の実データ（MaleCNS）**. Keep `/brain-data/manifest.json` as the data URL,\nthen click **選んだ脳で会話を始め直す**. Use **動きを試す** to inspect activity without\nan API key. For real chat, select a provider/model and enter any required API key\nin the same settings dialog.\n\nTo use data prepared elsewhere, set the directory at startup. With this npm\nworkspace command, relative paths resolve from `packages/noise`:\n\n```sh\nMALECNS_DATA_DIR=./data/malecns-v1-retry \\\n  npm -w @aituber-onair/noise run example:brain-chat\n```\n\nFor real responses through the Codex SDK, follow the\n[CLI connection instructions](examples/neural-rewrite-cli/README.md#run-with-codex-sdk).\nInstall the SDK separately and select real wiring with `MALECNS_DATA_DIR`.\nSee the [WebUI README](examples/noise-brain-chat/README.md) for hosting and controls.\n\nIn your own Node.js application, place the converted directory where the app can\nread it and pass that path to `loadMaleCnsNoiseBrain({ dataDir })` as shown below.\nThe original Feather files are not needed at runtime.\n\n**Hosting and redistribution**\n\nThe data is provided by [MaleCNS](https://male-cns.janelia.org/) under\n[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). Include `ATTRIBUTION.txt`\nwhen distributing converted data, and retain source credits, a license link and\nan explanation of the modifications. A hosted app should make these available\nin its data information or credits. The local development server serves the four\ndata files above, not `ATTRIBUTION.txt`.\n\n#### Conversion and loading details\n\nPreparation uses Apache Arrow 21.2 and an LZ4 decoder, installed by `npm ci` above.\nIt scans Feather connectivity batches twice without creating a JavaScript object\nper edge. Optional `--signs signs.json` replaces the transmitter-sign table;\nunknown names always have sign zero.\n\nRetained entries have non-empty `superclass`, excluding glia. All connections\nbetween retained entries remain, including self-connections and zero-effective\nweights, without a synapse threshold. Preparation requires exactly 166,700\nneurons and 25,582,938 directed edges for this release. Changes fail validation.\n`consensus_nt` supplies the transmitter: acetylcholine is positive; GABA,\nglutamate, and histamine are negative; modulators and unknown transmitters\nhave no fast effect. Signed weights are divided by the target's incoming\nabsolute signed strength. This is a configurable simulation assumption,\nnot a universal description of neurotransmitter effects.\n\nThe six input channels deterministically partition annotated LC4, LPLC2,\nLPLC1, and LC10a populations. These channel assignments have no claimed\nconversational or sensory equivalence. Descending neurons are identified by\n`superclass === 'descending_neuron'`.\n\n```ts\nimport { loadMaleCnsNoiseBrain } from '@aituber-onair/noise/node';\n\nconst brain = await loadMaleCnsNoiseBrain({\n  dataDir: './packages/noise/data/malecns-v1',\n  seed: 42,\n});\n// Pass brain as createContaminator({ model, modulator: brain }).\n```\n\nRuntime verifies the manifest's version/counts and the graph SHA-256 before\nvalidating the graph layout. The manifest records source hashes, preprocessing\noptions, and generation date. Use a trusted manifest: a hash is an integrity\ncheck, not authentication of its publisher.\n\n### Browser deployment and workers\n\nDevelopers prepare/download the files in advance and host `manifest.json` and\n`graph.bin` at an explicitly configured application URL. A GitHub Release can\ndistribute prepared files to developers; this prototype does not publish a\nRelease or silently fetch one. Serving a file near the application still\nrequires the browser to transfer it when loaded. Keep the virtual backend as\nthe normal experience and make the large backend an explicit application choice.\n\nFor CPU isolation, create a module Worker using your application's bundler.\nThe package ships no preconfigured worker URL or automatic dataset URL.\n\n```ts\n// brain.worker.ts: bind immediately so requests can wait for initialization.\nimport { exposeNoiseBrainWorker, loadMaleCnsNoiseBrain } from '@aituber-onair/noise/web';\nexposeNoiseBrainWorker(self, loadMaleCnsNoiseBrain({\n  manifestUrl: '/data/malecns-v1/manifest.json',\n}));\n// For the small backend use createVirtualNoiseBrain() instead.\n```\n\n```ts\n// Application: a larger initial deadline accommodates loading the large graph.\nimport { createWorkerNoiseModulator } from '@aituber-onair/noise/web';\nconst worker = new Worker(new URL('./brain.worker.ts', import.meta.url), {\n  type: 'module',\n});\nconst modulator = createWorkerNoiseModulator(worker, 30_000);\nconst contaminator = createContaminator({\n  model, modulator, modulatorTimeoutMs: 31_000,\n  fallbackToDraftOnQualityFail: true,\n});\n// Call modulator.dispose() when the application no longer needs the worker.\n```\n\nWorker failures/timeouts reject pending requests and timeouts terminate the\nworker. Later calls fail immediately so Noise can continue without modulation.\nRecreate the worker to retry. The built-in protocol returns small modulation\nsummaries only; per-neuron visualization snapshots need an application-specific\nmessage in the worker. HTTPS or localhost is needed for Web Crypto verification.\nNo SharedArrayBuffer or cross-origin-isolation headers are required.\n\n### Activity inspection, data layout, and validation\n\n`brain.getActivitySnapshot()` returns a detached `Uint32Array` of last-turn spike\ncounts; `brain.getBodyIds()` returns corresponding IDs. Snapshots are created\nonly on request. These are simulated activity counts, not proof of a neuron's\ncausal role. The virtual backend's IDs are synthetic.\n\nPrepared files include optional `metadata.json` and `soma-positions.f32` for\nfuture visualization. The latter stores XYZ Float32 soma locations in MaleCNS\nEM voxel coordinates (8 nm), with NaN for absent positions. It adds 2,000,400\nbytes; neither file is loaded by the simulation. A point view can join IDs to\nthese positions, but detailed neuron branches, brain meshes, a fly body,\nand animated movement require additional geometry and rendering. This package\ndoes not include a renderer or NeuroMechFly.\n\n`graph.bin` uses little-endian 32-bit values: magic `0x3142524e`, format version\n1, neuron count, edge count; then offsets (N+1), body IDs (N), flags (N), targets\n(E), Float32 weights (E). Flags contain descending membership (bit 0), left\n(bit 1), right (bit 2), and neutral input channel (bits 8–15; 255 means none).\nThe complete retained graph is 206,663,924 bytes, before optional metadata.\n\n```sh\nnpm -w @aituber-onair/noise run example:brain\n# After building, optionally validate/load and exercise the actual dataset:\nMALECNS_DATA_DIR=./packages/noise/data/malecns-v1 \\\n  node packages/noise/scripts/brain-example.mjs\n```\n\nThe example compares plans with and without a brain and reports loading time,\nsimulation time, activity, and process memory. Its offline rewrite stub returns\nthe draft; this verifies plumbing, not improved language quality. Real data is\nnever required by CI. Tests use a synthetic graph with the same binary layout.\nTypeScript is the initial numerical backend; no Wasm artifact or native build\nis required. Backend interfaces keep a future kernel replacement possible.\n\nData attribution: [MaleCNS project](https://male-cns.janelia.org/), FlyEM\n(HHMI Janelia), University of Cambridge, MRC Laboratory of Molecular Biology,\nand Google Research. The dataset is [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).\nPrepared output includes attribution and describes the transformation.\nThis simulator is independently implemented; no code from other fly simulators\nis copied.\n\n### Chat and neural activity sample\n\n```sh\nnpm -w @aituber-onair/noise run example:brain-chat\n```\n\nSelect a provider and model through `@aituber-onair/chat`, compare the draft and Noise response,\nand replay recorded neural activity. Switch between virtual and prepared\nMaleCNS backends, then inspect IDs, annotations, and spike counts.\nThe sample opts into `captureActivity: true` and `brain.getActivityTrace()`;\nnormal consumers do not need temporal recording. See the\n[sample README](./examples/noise-brain-chat/README.md) for connection settings,\ndata hosting, visualization semantics, and the offline demonstration mode.\n\nSee the [neural rewrite CLI](./examples/neural-rewrite-cli/README.md) for repeated\nCodex SDK evaluations of the same composition pipeline.\n\n### State-driven neural responses\n\n```ts\nimport { createContaminator, createVirtualNoiseBrain,\n  createNeuralReactionModel, createChatRewriteModel } from '@aituber-onair/noise';\n\nconst brain = createVirtualNoiseBrain({\n  seed: 42, steps: 32, captureReadout: true, retainState: true,\n});\nconst noise = createContaminator({\n  model: createNeuralReactionModel({\n    brain,\n    model: createChatRewriteModel({ service: chatService }),\n    onTrace: (trace) => console.log(trace.state),\n  }),\n  mode: 'chaotic',\n  intensity: 0.9,\n  relationshipCapital: 0.8,\n  quality: { minLengthRatio: 0.8, maxLengthRatio: 1.1 },\n  fallbackToDraftOnQualityFail: true,\n});\n```\n\n`chatService` is a configured AITuber OnAir Chat service. An encoder classifies six\nstimulus channels from the incoming conversation. Code splits the original into\nlossless clauses and binds each clause to a balanced readout-population projection\nusing a fixed hash. Actual spikes produce separate early/late attention weights\nover those clauses. The writer receives the original, persona and these weights.\nThe binding is artificial, not learned semantics or fly language. Different\nwording can bind different cells; no random writing-style selector is used.\n\nThe original is a starting point: meaning, conclusion, momentary feelings and\nimmediate intentions may change, and source details may be omitted. Preserve a\nrecognizable character and a coherent connection to the conversation. Do not invent\npast events or external facts. Protected numbers, URLs and code remain intact. The adapter opts\ninto `shift_attention` planning while sincerity, relationship and rhythm gates\nremain. Restored length is limited to 0.8–1.1 times the original. The successful\npath uses three model calls: classification, speech, coherence/character/attention audit.\nTwo candidates are audited in order, with one speech retry at most (seven calls),\nthen fallback. Weak attention also returns the original as `neural_unfocused`.\nModel checks cannot establish corpus diversity or neural causality.\n\n`retainState: true` preserves membrane and pending synaptic state across turns, with\nan 8 ms simulated quiet interval; it does not track wall-clock time. Use a separate\ninstance per conversation and call `brain.reset()` to start over. The default false\nretains the original per-turn reset behavior. `onTrace` reports stimuli, source\nclauses (`facts`/`anchors`), `attention`, spike summaries, proposed texts and rejection\nreasons. Legacy `state` is diagnostic and no longer controls speech. Final acceptance belongs to `output.text`\nand `output.rewriteTrace`; core quality checks may still reject a generated response.\n\n`captureReadout: true` is required and stores small temporal summaries without full\nvisualization frames. Custom modulators must accept `readoutKeys` and return aligned\n`contentKeys` and per-frame `contentAxes`; missing readouts fall back to the draft.\nPass the same options to the real-wiring loader when using prepared data. Wiring is\nnever sent to the LLM. This is opt-in; ordinary Noise behavior is unchanged.\nSee the [CLI sample](examples/neural-rewrite-cli/README.md) for execution and evaluation.\n\n### Experimental neural working memory\n\n`createNeuralWorkingMemory` recalls source conversation episodes through a small\nstateful spiking circuit. The caller provides embeddings and passes the recalled\nmessages to any language model. This path generates a response from recalled\ncontext without rewriting a draft or sending style controls. It changes model\ninput, not hidden activations; conversation quality and an advantage over ordinary\nretrieval are not established. It is a separate opt-in virtual circuit, with no\nautomatic model or wiring downloads.\n\nSee the [working-memory CLI](examples/neural-memory-cli/README.md) for the input\nformat, state lifecycle, provider integration and limitations.\n","readmeFilename":"README.md"}