import XCTest
import Metal
@testable import PhysarumEngine
/// Regression guard for the `SimEngine.restore()` release-mode miscompile (2026-06-13):
/// calling the ObjC method `MTLBuffer.contents()` inside a `withUnsafeBytes` noescape closure
/// within a `throws` function corrupted the error register under -O, so `restore` appeared to
/// throw a garbage error and callers crashed (EXC_BAD_ACCESS, masked by ASan/Debug).
/// NOTE: only reproduces in RELEASE (`swift test -c release`); Debug optimizes differently.
final class RestoreReleaseRegressionTests: XCTestCase {
func testRestoreDoesNotCorruptThrows() throws {
guard let device = MTLCreateSystemDefaultDevice() else { throw XCTSkip("no gpu") }
let lib = ShaderLibrary(device: device)
let preset = try TestFixtures.minimalPreset().params
let src = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
for _ in 0..<10 { src.step() }
let state = src.snapshot()
let dst = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
try dst.restore(state) // crashed pre-fix: garbage error on the throws return
XCTAssertFalse(dst.pheromoneStats().hasNaN)
}
func testRestoreThenRenderOffscreen() throws {
guard let device = MTLCreateSystemDefaultDevice() else { throw XCTSkip("no gpu") }
let lib = ShaderLibrary(device: device)
let preset = try TestFixtures.minimalPreset().params
let src = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
for _ in 0..<10 { src.step() }
let state = src.snapshot()
let r = try Renderer(device: device, library: lib, width: 256, height: 256)
let dst = try SimEngine(device: device, library: lib, width: 256, height: 256, preset: preset)
try dst.restore(state)
r.resetFeedback()
let d = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .bgra8Unorm, width: 512, height: 512, mipmapped: false)
d.usage = [.renderTarget, .shaderRead]; d.storageMode = .shared
let target = try XCTUnwrap(device.makeTexture(descriptor: d))
try r.renderOffscreen(sim: dst, preset: preset, into: target, cover: true)
XCTAssertNotNil(r.cgImage(target))
}
}