Skip to content

MotionInteropSmoke.kt

src/jvmMain/kotlin/afield/animation/mocap/MotionInteropSmoke.kt · 63 lines

package afield.animation.mocap

import org.json.JSONObject
import java.nio.file.Files
import java.nio.file.Path
import java.util.concurrent.TimeUnit

FIELD_TEST id: motion.recording.interop runner: jvm-main entrypoint: afield.animation.mocap.MotionInteropSmoke tags: [smoke, animation, motion, python] timeoutSec: 30 expect: exitCode: 0 stdoutContains: - "Motion recording interoperability passed"

object MotionInteropSmoke

Real Kotlin -> Python -> Kotlin binary round trip; no network inference is claimed.

@JvmStatic fun main(args: Array<String>)

body — 41 lines, from line 22
    @JvmStatic fun main(args: Array<String>) {
        val root = Files.createTempDirectory("field-motion-interop")
        try {
            val source = root.resolve("kotlin.f4motion")
            val target = root.resolve("python.f4motion")
            MotionExamples.recordContractClip(source)
            val script = """
                import sys
                from field_motion.recording import Recording, RecordingWriter
                r = Recording(sys.argv[1])
                frames = list(r.frames())
                assert len(frames) == 81
                assert {f['episode'] for f in frames} == {0, 1}
                with RecordingWriter(sys.argv[2], r.skeleton, {'backend': 'interop-test'}, chunk_frames=7) as w:
                    for f in frames: w.append(f)
                    for e in r.events(): w.event(e)
                print(r.skeleton['hash'])
            """.trimIndent()
            val interpreter = System.getenv("FIELD_MOTION_TOOLS_PYTHON") ?: if (System.getProperty("os.name").startsWith("Windows")) "python" else "python3"
            val log = root.resolve("python.log")
            val process = ProcessBuilder(interpreter, "-c", script, source.toString(), target.toString())
                .redirectErrorStream(true).redirectOutput(log.toFile()).apply {
                    environment()["PYTHONPATH"] = Path.of("optional/motion/python").toAbsolutePath().toString()
                }.start()
            if (!process.waitFor(20, TimeUnit.SECONDS)) { process.destroyForcibly(); error("Motion interop Python timed out") }
            check(process.exitValue() == 0) { Files.readString(log) }
            val a = MotionRecording(source); val b = MotionRecording(target)
            check(a.skeleton.hash == b.skeleton.hash)
            val before = a.frames().toList(); val after = b.frames().toList()
            check(before.size == after.size)
            before.zip(after).forEach { (x, y) ->
                check(x.time == y.time && x.index == y.index && x.episode == y.episode)
                check(x.executed.root == y.executed.root && x.executed.localRotations == y.executed.localRotations && x.executed.worldPositions == y.executed.worldPositions)
            }
            check(b.events().single().getString("type") == "reset")
            println("Motion recording interoperability passed: 81 frames, two episodes, identical float payloads")
            // Optional retained recording for benchmarking/inspection outside a test.
            if (args.isNotEmpty()) MotionExamples.recordContractClip(Path.of(args[0]))
        } finally { root.toFile().deleteRecursively() }
    }
}