auto2.0 / scripts /render_papercraft_test.py
Antigravity
feat: implement papercraft minimal theme & solid hydraulic compactor press
8c9e470
Raw History Blame Contribute Delete
4.02 kB
"""Render local test video for Papercraft Minimal theme with Solid Hydraulic Press Compactor."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
import time
from pathlib import Path
from raceforge.audio.mix import AudioSynthesizer
from raceforge.director.base import TrackDirective
from raceforge.director.procedural import ProceduralDirector
from raceforge.encode.ffmpeg_pipe import encode_full_mp4
from raceforge.render.frames import CairoFrameRenderer
from raceforge.sim.engine import SimulationEngine
from raceforge.track.builder import build_track
from raceforge.track.clearance import compute_walkable_manifold
from raceforge.track.solver import GeodesicSolver
def main() -> None:
seed = 513
print("=" * 65)
print(f" Rendering Papercraft Minimal Test Video (Seed {seed})")
print("=" * 65)
out_dir = Path("out")
out_dir.mkdir(parents=True, exist_ok=True)
mp4_path = out_dir / "papercraft_hydraulic_test.mp4"
wav_path = out_dir / "papercraft_hydraulic_test.wav"
director = ProceduralDirector()
directive = director.direct(seed)
directive.theme = "paper"
print("\n[1/4] Building track and simulating physics...")
spec, grid = build_track(seed, directive)
spec.countdown_s = 0.0 # Direct start
spec.flood_wall_enabled = True
spec.flood_wall_duration_s = 45.0
goal_cell = grid.world_to_cell(
(spec.track.goal_tile[0] + spec.track.goal_tile[2]) / 2.0,
(spec.track.goal_tile[1] + spec.track.goal_tile[3]) / 2.0,
)
assert goal_cell is not None
walkable = compute_walkable_manifold(grid, spec.physics.racer_size)
solver = GeodesicSolver(grid, walkable, goal_cell)
engine = SimulationEngine(spec, grid, solver, fps=60, max_duration_s=70.0)
timeline = engine.run()
outcome = timeline.outcome
winner_name = outcome.winner.value.upper() if outcome.winner else "NONE"
dur_s = outcome.total_duration_s
print(f" • Duration: {dur_s:.2f}s ({timeline.total_frames} frames @ 60 FPS)")
print(f" • Winner: {winner_name}")
print(f" • Eliminated racers: {len(outcome.eliminated)} ({[r.value for r in outcome.eliminated]})")
print(f" • Survivors: {len(spec.racers) - len(outcome.eliminated)}")
print(f" • Total Bounces: {outcome.total_bounces}")
print("\n[2/4] Synthesizing pentatonic audio with exact contact synchronization...")
synth = AudioSynthesizer(spec, timeline)
synth.synthesize(wav_path)
print(f" ✓ Audio synthesized: {wav_path} ({wav_path.stat().st_size / 1024:.1f} KB)")
print("\n[3/4] Rendering 1080x1920 60 FPS video frames with Cairo...")
t0 = time.time()
renderer = CairoFrameRenderer(spec, grid, timeline)
encode_full_mp4(
renderer.iter_raw_bgra_frames(),
wav_path,
mp4_path,
width=1080,
height=1920,
fps=60,
)
render_s = time.time() - t0
print(f" ✓ Video encoded in {render_s:.1f}s ({timeline.total_frames / max(0.1, render_s):.1f} fps)")
print(f" ✓ Output size: {mp4_path.stat().st_size / 1024 / 1024:.2f} MB")
print("\n[4/4] Extracting high-resolution preview frames...")
frames_to_extract = [
("frame_1_t1s.png", 1.0),
("frame_2_t12s.png", 12.0),
("frame_3_t24s.png", 24.0),
("frame_4_t35s.png", 35.0),
("frame_5_finish.png", max(1.0, dur_s - 0.5)),
]
for fname, ts in frames_to_extract:
fpath = out_dir / fname
cmd = [
"ffmpeg",
"-y",
"-ss",
f"{ts:.2f}",
"-i",
str(mp4_path),
"-frames:v",
"1",
"-q:v",
"2",
str(fpath),
]
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
print(f" ✓ Extracted preview: {fpath.name} at t={ts:.1f}s")
print("\nDone! All test assets generated successfully.")
if __name__ == "__main__":
main()