fix(pet): keep Apple audio aligned without stopping the world

Transfer checked Float32 PCM directly from JavaScriptCore. Re-anchor native
presentation after UI stalls, retire stale voice callbacks by ticket, and
mute output failures while preserving world ticks, saves and exports.

Validation: npm test && npm run check:web passed: 66 package, 14 SDK,
446 web; 0 failures, 0 web errors, 2 existing warnings. Pet check 63/0/0.
Actual TUI Watch 12/0; Android instrumentation 11/0 with APK/lint build.
Apple 11 checkpoint workflows plus 27 exact PCM ranges and malformed guards;
final macOS compile/sign/verify and iOS Simulator build passed.
Real AVAudioEngine completed 150 frames with a 750ms stall, all 12 onsets
scheduled in the future. Previous host fails the injected PCM-error control;
current host mutes sound, continues world ticks and exports. Audio-frame p95
70.6ms -> 23.4ms in synthetic profiling; concurrent-machine wall outliers
remain, so no hard frame-time or subjective listening guarantee is claimed.

Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
CodeWhale Bot committed 2026-09-12 15:21:01 -07:00
1 parent 5fd45f13ae
commit ee35326e7f
10 files changed
+196 -41

No files matched your search

+6 -3
View File
@@ -12,7 +12,7 @@ const pet_telemetry_js_1 = require("./pet-telemetry.js");
const pet_sim_js_1 = require("./pet-sim.js");
const pet_audio_js_1 = require("./pet-audio.js");
const pet_engine_js_1 = require("./pet-engine.js");
/** Synchronous JSON boundary for JavaScriptCore and embedded JS runtimes.
/** Synchronous native boundary: JSON state and directly transferable PCM.
* Native hosts share the actual world / score implementation, not a rewrite. */
class PetNative {
world;
@@ -101,9 +101,12 @@ class PetNative {
return JSON.stringify({ width, height, cells, timeMs: frame.timeMs, channel: frame.state.channel, arch: pet_sim_js_1.ARCH_OF[frame.state.channel],
hollow: frame.state.observed < .92, dozing: frame.behaviour === 'doze', lit: frame.state.lit });
}
pcm(voicesJSON, startSample, length, rate) {
pcmChannels(voicesJSON, startSample, length, rate) {
const p = (0, pet_audio_js_1.renderPetPCM)(JSON.parse(voicesJSON), startSample, length, rate);
return JSON.stringify([Array.from(p.left), Array.from(p.right)]);
return [p.left, p.right];
}
pcm(voicesJSON, startSample, length, rate) {
return JSON.stringify(this.pcmChannels(voicesJSON, startSample, length, rate).map(channel => Array.from(channel)));
}
}
exports.PetNative = PetNative;
+6 -4
View File
@@ -155,10 +155,12 @@ live delivery to finish before extending any request as current. These frames
are transport metadata; they do not enter the event journal or pet tape.
The Runtime importer separately pairs request
lifecycles and preserves failure receipt times. Its local SSE/CLI and browser
fixtures pass. Authenticated read-only observation of an existing local Runtime
journal is verified; new active-session delivery through the native companions
still needs acceptance QA. The
source and liveness contracts are documented in `pet/README.md`.
fixtures pass. Typed synthetic Engine events have also passed through the actual
Runtime journal/SSE endpoint, recorder process and Apple file watcher/PetHost:
a fresh request appeared at 1,233 ms and cleared after its answer at 2,000 ms.
That provider-free fixture proves active native file delivery; physical-phone
transport and visual popover inspection are separate evidence. The source and
liveness contracts are documented in `pet/README.md`.
The full TUI binary builds offline, its foreground metadata projection test
passes, and the real PTY walkthrough covers Watch selection, keyboard, mouse
+61 -4
View File
@@ -17,9 +17,10 @@ npm test
npm run check:web
```
The pet suite currently contains 48 tests: event occupancy/unknown coverage,
The pet suite currently contains 63 tests: event occupancy/unknown coverage,
late failures, human request pairing, read-only local SSE reconnect/cursor
recovery and cancellation after garbage collection, deterministic world/score/PCM,
recovery, replay readiness and cancellation after garbage collection, bounded
long sessions and recorder process restart, deterministic world/score/PCM,
checkpoint integrity and continuation, immutable segment boundaries, pending input
retention, browser saves overlapping source changes, live-file freshness/restarts,
delayed browser reads crossing suspension, expression-version validation and legacy replay.
@@ -67,6 +68,9 @@ workflows, including a generated two-hour synthetic unknown recording, segment
publication, a save conflict that must preserve the running world, and actual
live-file appends, pause/resume, in-place restarts, replacement and recreation. It writes
only disposable fixtures under ignored conformance results and temporary storage.
The same executable checks 27 tone/noise/long-clock PCM ranges at three sample
rates through the actual JavaScriptCore Float32 boundary, comparing sample bits
with the retained JSON path and rejecting malformed or nonfinite buffers.
Kotlin 2.3.0 can instead be on PATH; its verifier builds only the particle core
and conformance runner. The actual Compose application is a separate Gradle build:
@@ -182,8 +186,9 @@ open junctions; the same particle identities return to the whale at rest.
companion acceptance. The old idle-stream shutdown failure is covered by a
real HTTP regression fixture that forces garbage collection before closing.
Hosted CI and Grokbot findings are separate evidence. Their actual results belong
on the pull request. Open implementation gaps are listed in [README.md](README.md).
These chronological receipts describe the source at each milestone; later
sections supersede earlier open items. Hosted CI and Grokbot findings are separate
evidence on the pull request. Current limits are listed in [README.md](README.md).
## Recorder process restart — September 12, 2026
@@ -257,3 +262,55 @@ These are synthetic event fixtures with real transports, not provider calls.
World, score and native bundle bytes are unchanged. Hosted CI, independent
Codewhalebot QA, and active-session native delivery remain separate evidence.
## Apple audio and scope audit — September 12, 2026
Apple now transfers the shared generator's Float32 channels directly from
JavaScriptCore. Each channel's type, length and finite samples are checked; its
borrowed pointer is copied immediately while the JS value remains rooted.
The JSON PCM method remains available to QuickJS consumers. There is one score
and sample generator.
Actual AVAudioEngine playback exposed schedules more than a second in the past
after a 750 ms UI stall. Presentation now rebases against the current world and
retires stale voices after long gaps. Completion callbacks carry numeric tickets
back to the main actor, preventing an old completion from retiring a newer node.
A sound failure mutes and persists the sound preference while world ticks and
recording exports continue. The previous Apple host stops the world on the same
injected PCM failure; the corrected host advances from 400 ms to 933 ms and exports.
Local final-source receipts:
- Pet checks: 63 passed, zero failed/canceled. Required product gate: 66 package,
14 SDK and 446 web tests passed; web check has zero errors and two existing warnings.
- Apple: all 11 checkpoint/live-file/storage workflows and 27 exact PCM ranges
pass, plus empty/invalid range and four malformed-buffer checks. macOS builds,
signs and verifies; iOS Simulator builds from the same final Swift sources.
- Real AVAudioEngine: 150 frames complete with a forced 750 ms stall. All 12
scheduled onsets remain in the future (42–118 ms); the prior output drifts
down to 1,703 ms late. This proves scheduling, not subjective listening quality.
- An 80-second synthetic profile renders 122 voices. Audio-frame p95 falls from
70.6 ms on JSON transport to 23.4 ms on final typed transfer, with the same
final particle digest and sample peak. An earlier typed-transfer run had zero
frames over 33 ms; the final run had six wall-time outliers (worst 738 ms) on
the concurrently used machine. This is not a hard frame-time guarantee.
- The regenerated bundle passes 12 actual TUI Watch tests and all 11 Android
instrumentation tests together, plus APK assembly and lint. The score and
particle algorithms are unchanged.
The original A–F workstreams map to implemented, buildable source:
| Workstream | Implementation and evidence |
| --- | --- |
| A — World | Shared seeded behavior state machine, environment, interaction journal, doze/wake, persistent pod identities and versioned forms. Browser/Apple/Android replay and checkpoint workflows; 380 four-port conformance checkpoints. |
| B — Audio | One thirteen-category score and PCM generator; browser WebAudio, Apple AVAudioEngine, Android AudioTrack and opt-in TUI ffplay. Deterministic PCM, native cursor tests, Apple output/stall/failure receipts. Listening quality remains a separate human assessment. |
| C — TUI | Existing Engine events feed Watch through the canonical bucketer; bounded cameo honors open-water collision, palette and motion rules. Actual product build, PTY interaction and 12 Watch checks. |
| D — macOS | Locally signed LSUIElement application, menu-bar controls, launch-at-login option, Still/sound/source settings and DispatchSource live files. Real process persistence and host workflows; current popover inspection is unverified. |
| E — Mobile | SwiftUI Simulator app and Compose Android app use the same core and local-file contract. iOS build plus prior Simulator restoration/export/recovery; Android APK/lint plus 11 real-engine/device-lifecycle tests. No automatic phone link is claimed. |
| F — Live authority | Existing Engine journal and opt-in Runtime replay progress feed one event-v1 bucketer. Synthetic Engine → actual Runtime/SSE → recorder → native Apple host passes without provider calls or raw prompt capture. |
Source and build instructions are published on PR #6110. Commit-specific
Grokbot review and GitHub checks are recorded there. Shipping, signing for public
distribution and subjective audiovisual acceptance are not implied by these
local builds or by a prior commit's green CI.
+18 -9
View File
@@ -72,6 +72,11 @@ It journals accepted telemetry, interactions, behaviors and persistent pod slots
positions, so buffer partitioning cannot change the score. Audio mixing is a
host concern. `pet-native.ts` exposes this exact core to QuickJS/JavaScriptCore.
The TUI, Apple and Android hosts do not carry their own event bucketers or score schedulers.
Apple copies validated Float32 PCM channels directly from JavaScriptCore into
native buffers; it does not serialize sample arrays as JSON. AVAudioEngine
follows the world clock, rebases presentation after a stall, and discards stale
voices after a long gap. Device or rendering failures mute sound and report a
message while the world, persistence and export continue.
The Rust particle implementation lives in the product's
`crates/tui/src/tui/ambient_life/pet_sim.rs`; this package's runner imports it.
@@ -167,11 +172,13 @@ tail on an IO worker, closes it on pause/background, and discards delayed delive
The file must be updated by a producer; selecting a completed tape does not make
it live. Use Import to replay that tape. Device files are not automatically synced
from the desktop recorder.
Authenticated read-only attachment has been exercised against a running local
Runtime 0.9.13 and an existing session journal. Old completed work remains unknown
at the recorder's current clock. The recorder also closes an idle stream after
garbage collection. Receiving new active-session work through native companion
surfaces still needs acceptance QA.
The current live chain has been exercised with typed synthetic Engine events
through the actual Runtime journal/SSE endpoint, recorder process and Apple
file watcher/host. The world receives a fresh human request and clears it after
its answer; prompt, question and answer text stay out of the recording. This
uses the existing mock Engine handle without calling a provider. The older
read-only Runtime 0.9.13 receipt predates the required progress capability and
does not establish current compatibility.
## Persistence and current limits
@@ -214,7 +221,9 @@ A 30-hour synthetic Still run of the shared core rotated 263 segments and kept
the active file below 0.45 MB, with exact checkpoint continuation after every
rotation. Apple and Android separately exercise archive publication, failed saves
and continued execution in their actual embedded engines. This is not 30 hours
of animated native-device or power testing. Physical Android device acceptance,
macOS popover inspection, active-session companion acceptance and final
listening/power quality remain unfinished. This branch is a reviewable prototype,
not a release candidate.
of animated native-device or power testing. Current platform builds, replay,
live file delivery and measured audio output have separate receipts in [QA.md](QA.md).
Automatic desktop-to-phone transport is not included: mobile hosts consume a
local file using the shared contract. Physical-phone listening/battery quality
and current macOS popover inspection have not been established. The source is
available as a development build; this branch has not been released.
+6 -3
View File
@@ -12,7 +12,7 @@ const pet_telemetry_js_1 = require("./pet-telemetry.js");
const pet_sim_js_1 = require("./pet-sim.js");
const pet_audio_js_1 = require("./pet-audio.js");
const pet_engine_js_1 = require("./pet-engine.js");
/** Synchronous JSON boundary for JavaScriptCore and embedded JS runtimes.
/** Synchronous native boundary: JSON state and directly transferable PCM.
* Native hosts share the actual world / score implementation, not a rewrite. */
class PetNative {
world;
@@ -101,9 +101,12 @@ class PetNative {
return JSON.stringify({ width, height, cells, timeMs: frame.timeMs, channel: frame.state.channel, arch: pet_sim_js_1.ARCH_OF[frame.state.channel],
hollow: frame.state.observed < .92, dozing: frame.behaviour === 'doze', lit: frame.state.lit });
}
pcm(voicesJSON, startSample, length, rate) {
pcmChannels(voicesJSON, startSample, length, rate) {
const p = (0, pet_audio_js_1.renderPetPCM)(JSON.parse(voicesJSON), startSample, length, rate);
return JSON.stringify([Array.from(p.left), Array.from(p.right)]);
return [p.left, p.right];
}
pcm(voicesJSON, startSample, length, rate) {
return JSON.stringify(this.pcmChannels(voicesJSON, startSample, length, rate).map(channel => Array.from(channel)));
}
}
exports.PetNative = PetNative;
+6 -3
View File
@@ -4,7 +4,7 @@ import { ARCH_OF, digest, layout } from './pet-sim.js';
import { renderPetPCM, type PetVoice } from './pet-audio.js';
import { PetEngineTelemetry } from './pet-engine.js';
/** Synchronous JSON boundary for JavaScriptCore and embedded JS runtimes.
/** Synchronous native boundary: JSON state and directly transferable PCM.
* Native hosts share the actual world / score implementation, not a rewrite. */
export class PetNative {
private world: PetWorld;
@@ -80,8 +80,11 @@ export class PetNative {
return JSON.stringify({ width, height, cells, timeMs: frame.timeMs, channel: frame.state.channel, arch: ARCH_OF[frame.state.channel],
hollow: frame.state.observed < .92, dozing: frame.behaviour === 'doze', lit: frame.state.lit });
}
pcm(voicesJSON: string, startSample: number, length: number, rate: number): string {
pcmChannels(voicesJSON: string, startSample: number, length: number, rate: number): [Float32Array, Float32Array] {
const p = renderPetPCM(JSON.parse(voicesJSON) as PetVoice[], startSample, length, rate);
return JSON.stringify([Array.from(p.left), Array.from(p.right)]);
return [p.left, p.right];
}
pcm(voicesJSON: string, startSample: number, length: number, rate: number): string {
return JSON.stringify(this.pcmChannels(voicesJSON, startSample, length, rate).map(channel => Array.from(channel)));
}
}
+26 -9
View File
@@ -5,7 +5,8 @@ import AVFoundation
/// schedules bounded buffers against AVAudioEngine's presentation clock.
@MainActor public final class PetAudioOutput {
private let engine = AVAudioEngine()
private var active: [AVAudioPlayerNode] = []
private var active: [UInt64: AVAudioPlayerNode] = [:]
private var nextVoiceID: UInt64 = 0
private let format = AVAudioFormat(standardFormatWithSampleRate: 48_000, channels: 2)!
public private(set) var enabled = false
private var epoch: UInt64 = 0
@@ -15,6 +16,7 @@ import AVFoundation
public func setEnabled(_ value: Bool, simulationTime: Double) throws {
stop()
if value {
guard simulationTime.isFinite && simulationTime >= 0 else { throw PetCoreError.invalid("Invalid audio presentation time.") }
// Materialize the output graph before starting. With no voices yet,
// AVAudioEngine otherwise raises an Objective-C exception (not Error).
_ = engine.mainMixerNode; _ = engine.outputNode
@@ -23,15 +25,28 @@ import AVFoundation
}
enabled = value
}
private func discardVoices() {
for node in active.values { node.stop(); engine.detach(node) }
active.removeAll()
}
public func stop() {
for node in active { node.stop(); engine.detach(node) }
active.removeAll(); engine.stop(); enabled = false
discardVoices(); engine.stop(); enabled = false
}
public func present(_ voices: [PetVoice], core: PetNativeCore) throws {
guard enabled else { return }
guard engine.isRunning else { throw PetCoreError.invalid("The audio output device stopped.") }
let now = mach_absolute_time(), simulationTime = core.frame.timeMs / 1000
let drift = AVAudioTime.seconds(forHostTime: now - epoch) - (simulationTime - simulationStart)
// Device time continues through a stalled UI. Re-anchor presentation to
// the current world instead of playing an ever-growing delayed score.
// Small corrections let current buffers finish; long gaps discard them.
if abs(drift) > 0.05 {
if abs(drift) > 0.25 { discardVoices() }
epoch = now; simulationStart = simulationTime
}
// A stopped/replaced replay cannot leave an old voice attached.
active.removeAll { node in
if !node.isPlaying { engine.detach(node); return true }; return false
active = active.filter { _, node in
if !node.isPlaying { engine.detach(node); return false }; return true
}
for voice in voices {
if active.count >= 32 { break }
@@ -45,14 +60,16 @@ import AVFoundation
let node = AVAudioPlayerNode(); engine.attach(node); engine.connect(node, to: engine.mainMixerNode, format: format)
let elapsed = max(0, Double(start) / 48_000 - simulationStart + 0.1)
let when = AVAudioTime(hostTime: epoch + AVAudioTime.hostTime(forSeconds: elapsed))
let identity = ObjectIdentifier(node)
// A numeric ticket crosses the callback boundary; AVAudio nodes
// stay on the main actor and address reuse cannot retire a new voice.
nextVoiceID += 1; let identity = nextVoiceID
node.scheduleBuffer(buffer, at: nil, options: [], completionCallbackType: .dataPlayedBack) { [weak self] _ in
Task { @MainActor in
guard let self, let node = self.active.first(where: { ObjectIdentifier($0) == identity }) else { return }
node.stop(); self.engine.detach(node); self.active.removeAll { $0 === node }
guard let self, let node = self.active.removeValue(forKey: identity) else { return }
node.stop(); self.engine.detach(node)
}
}
node.play(at: when); active.append(node)
active[identity] = node; node.play(at: when)
}
}
}
+3 -2
View File
@@ -150,13 +150,14 @@ public func petArchiveLabel(_ name: String) -> String {
}
private func configureSound() {
do { try audio.setEnabled(sound && !paused && !failed && !restoring, simulationTime: (core?.frame.timeMs ?? 0) / 1000) }
catch { message = "Sound unavailable: \(error.localizedDescription)" }
catch { sound = false; message = "Sound unavailable: \(error.localizedDescription)" }
}
private func tick() {
guard !paused, !failed, !restoring, let core else { return }
do {
let f = try core.tick(motion: !(still || systemReducedMotion))
try audio.present(f.voices, core: core)
do { try audio.present(f.voices, core: core) }
catch { sound = false; message = "Sound unavailable: \(error.localizedDescription)" }
count += 1; if count % 150 == 0 { save() }
objectWillChange.send()
} catch { audio.stop(); failed = true; message = error.localizedDescription }
+22 -3
View File
@@ -141,8 +141,27 @@ public enum PetCoreError: Error, LocalizedError {
public func pcm(voice: PetVoice, startSample: Int, length: Int, rate: Int) throws -> [[Float]] {
let voices = String(decoding: try JSONEncoder().encode([voice]), as: UTF8.self)
failure = nil
let text = world.invokeMethod("pcm", withArguments: [voices, startSample, length, rate])?.toString()
guard let text, failure == nil else { throw PetCoreError.invalid(failure ?? "Unable to render pet audio.") }
return try JSONDecoder().decode([[Float]].self, from: Data(text.utf8))
guard let channels = world.invokeMethod("pcmChannels", withArguments: [voices, startSample, length, rate]), failure == nil,
channels.isArray, channels.forProperty("length")?.toInt32() == 2
else { throw PetCoreError.invalid(failure ?? "Unable to render pet audio.") }
return try (0..<2).map { index in
guard let channel = channels.atIndex(index) else { throw PetCoreError.invalid("Missing pet audio channel.") }
let ctx = context.jsGlobalContextRef
var exception: JSValueRef?
guard JSValueGetTypedArrayType(ctx, channel.jsValueRef, &exception) == kJSTypedArrayTypeFloat32Array,
exception == nil, let object = JSValueToObject(ctx, channel.jsValueRef, &exception), exception == nil,
JSObjectGetTypedArrayLength(ctx, object, &exception) == length, exception == nil
else { throw PetCoreError.invalid("Invalid pet PCM buffer.") }
if length == 0 { return [] }
guard let bytes = JSObjectGetTypedArrayBytesPtr(ctx, object, &exception), exception == nil
else { throw PetCoreError.invalid("Pet PCM buffer is unavailable.") }
// JavaScriptCore guarantees this pointer only until its next API
// call. Copy immediately; no JS-backed memory escapes the method.
let samples = withExtendedLifetime(channel) {
Array(UnsafeBufferPointer(start: bytes.assumingMemoryBound(to: Float.self), count: length))
}
guard samples.allSatisfy(\.isFinite) else { throw PetCoreError.invalid("Invalid pet PCM samples.") }
return samples
}
}
}
+42 -1
View File
@@ -1,5 +1,6 @@
import Foundation
import Darwin
import JavaScriptCore
@main struct AppleCheckpointProof {
static func require(_ condition: @autoclosure () throws -> Bool, _ message: String) throws {
@@ -23,12 +24,52 @@ import Darwin
@MainActor static func main() async throws {
let args = CommandLine.arguments
let pet = URL(fileURLWithPath: args[1]), out = URL(fileURLWithPath: args[2])
let bundle = Bundle(url: pet.appendingPathComponent("macos/CodewhalePet.app"))!
let bundle = Bundle(url: args.count > 3 ? URL(fileURLWithPath: args[3]) : pet.appendingPathComponent("macos/CodewhalePet.app"))!
let script = bundle.url(forResource: "pet-native", withExtension: "js")!
let tape = try String(contentsOf: bundle.url(forResource: "demo", withExtension: "jsonl")!, encoding: .utf8)
let points = try String(contentsOf: pet.appendingPathComponent("public/whale-points.tsv"), encoding: .utf8).split(separator: "\n").map { line -> (Double, Double) in
let v = line.split(separator: "\t").map { Double($0)! }; return (v[0], v[1])
}
// Exercise the actual JavaScriptCore typed-array boundary, including
// partial and empty ranges, against the retained JSON transport.
let referenceContext = JSContext()!
referenceContext.evaluateScript(try String(contentsOf: script, encoding: .utf8))
let pointJSON = String(decoding: try JSONSerialization.data(withJSONObject: points.map { [$0.0, $0.1] }), as: UTF8.self)
let reference = referenceContext.objectForKeyedSubscript("PetNative")!.construct(withArguments: [pointJSON])!
let audioCore = try PetNativeCore(points: points, bundle: script)
let audioVoices = [
PetVoice(id: "tone", start: 0, duration: 0.44, frequency: 196, gain: 0.04, pan: -0.6, kind: "tone"),
PetVoice(id: "noise", start: 12.033, duration: 0.22, frequency: 740, gain: 0.05, pan: 0.4, kind: "noise"),
PetVoice(id: "long-clock", start: 600_000, duration: 0.3, frequency: 49, gain: 0.03, pan: 0, kind: "tone"),
]
var pcmCases = 0
for voice in audioVoices {
let voiceJSON = String(decoding: try JSONEncoder().encode([voice]), as: UTF8.self)
for rate in [8000, 48000, 96000] {
for offset in [-8, 0, 31] {
let start = max(0, Int((voice.start * Double(rate)).rounded(.down)) + offset), count = rate / 10
let transferred = try audioCore.pcm(voice: voice, startSample: start, length: count, rate: rate)
let json = reference.invokeMethod("pcm", withArguments: [voiceJSON, start, count, rate])!.toString()!
try require(referenceContext.exception == nil, "Reference PCM failed")
let expected = try JSONDecoder().decode([[Float]].self, from: Data(json.utf8))
try require(transferred.map { $0.map(\.bitPattern) } == expected.map { $0.map(\.bitPattern) }, "Typed PCM samples differ from JSON at \(rate) Hz / \(offset)")
pcmCases += 1
}
}
}
try require(audioCore.pcm(voice: audioVoices[0], startSample: 0, length: 0, rate: 48000) == [[], []], "Empty PCM range rejected")
try reject({ _ = try audioCore.pcm(voice: audioVoices[0], startSample: -1, length: 4, rate: 48000) }, "Invalid PCM range accepted")
let pcmRoot = out.appendingPathComponent("apple-pcm-fixture-" + UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: pcmRoot, withIntermediateDirectories: true)
let originalScript = try String(contentsOf: script, encoding: .utf8)
for (index, response) in ["null", "[new Float64Array(n), new Float64Array(n)]", "[new Float32Array(n + 1), new Float32Array(n)]", "[new Float32Array(n).fill(NaN), new Float32Array(n)]"].enumerated() {
let invalid = pcmRoot.appendingPathComponent("invalid-\(index).js")
try (originalScript + "\nPetNative.prototype.pcmChannels = function(v, s, n, r) { return " + response + "; };\n").write(to: invalid, atomically: true, encoding: .utf8)
let badCore = try PetNativeCore(points: points, bundle: invalid)
try reject({ _ = try badCore.pcm(voice: audioVoices[0], startSample: 0, length: 4, rate: 48000) }, "Malformed PCM channel accepted")
}
print("PASS Apple PCM transfer: \(pcmCases) exact tone/noise/long-clock sample ranges, empty input and malformed-buffer rejection")
for input in ["", tape] {
let original = try PetNativeCore(points: points, bundle: script, tape: input)
for i in 0..<750 {