feat(godot): --shot-sequence per-frame capture for verifying paced animation
Dumps one PNG per _Process frame (auto-advancing past input waits), so a time-based sleep-paced effect can be verified as distinct frames. Confirmed the SC0000 opening now steps through paced AE*/character/CG frames instead of jumping straight to the final state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,7 @@ texture ops (no GPU context) — run windowed for real scenes. User args (after
|
||||
- `--seed 0xADDR=VAL` (repeatable) — seed initial global state, e.g. `--seed 0xa57=1` unlocks Lily's form-A voiced dialogue.
|
||||
- `--boot` — run SYSTEM4's state prefix (`INITCONFIG/INIT2/INIT`) via `GameSession` before the scene, so scene-assumed boot state (chiefly INIT2's gfx handle array) is present. **Needed for the gfx CGs to render** (without it the opening event CGs collapse/drift). e.g. `godot --path godot -- --boot`.
|
||||
- `--shot <png> [--shot-page N]` — capture page N to a PNG then quit (dev screenshot). At scene end it also prints the call-scripts executed as nested frames.
|
||||
- `--shot-sequence <dir> [--frames N]` — dump one PNG per rendered frame (`frame_0000.png…`, default N=180 ≈ 3s @60fps) then quit, auto-advancing past input waits. Verifies **time-based (sleep-paced) effects** — e.g. the opening `AE*` burst — as distinct frames, which a single `--shot` cannot. CPU/IO-heavy by design (a PNG every frame); a dev diagnostic, not a normal run. e.g. `godot --path godot -- --boot --shot-sequence out/seq --frames 300`.
|
||||
|
||||
## Asset resolution / graphics
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ public partial class Main : Godot.Control
|
||||
private int _shotSettleTarget = 3; // --shot-settle <frames>: settle N frames before grabbing (to
|
||||
// capture mid-tween — the anim clock keeps running while parked)
|
||||
private bool _shotDone;
|
||||
private string? _seqDir; // --shot-sequence <dir>: dump one PNG per frame (verify paced anim)
|
||||
private int _seqFrames = 180; // --frames <n>: how many frames to dump (default ~3s @60fps)
|
||||
private int _seqIdx;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -87,6 +90,8 @@ public partial class Main : Godot.Control
|
||||
if (userArgs[i] == "--shot" && i + 1 < userArgs.Length) _shotPath = userArgs[i + 1];
|
||||
if (userArgs[i] == "--shot-page" && i + 1 < userArgs.Length) int.TryParse(userArgs[i + 1], out _shotPage);
|
||||
if (userArgs[i] == "--shot-settle" && i + 1 < userArgs.Length) int.TryParse(userArgs[i + 1], out _shotSettleTarget);
|
||||
if (userArgs[i] == "--shot-sequence" && i + 1 < userArgs.Length) _seqDir = userArgs[i + 1];
|
||||
if (userArgs[i] == "--frames" && i + 1 < userArgs.Length) int.TryParse(userArgs[i + 1], out _seqFrames);
|
||||
if (userArgs[i] == "--seed" && i + 1 < userArgs.Length)
|
||||
{
|
||||
var kv = userArgs[i + 1].Split('=');
|
||||
@@ -128,12 +133,26 @@ public partial class Main : Godot.Control
|
||||
// --shot: auto-advance up to (but not past) the target page, then _Process captures + quits.
|
||||
if (_shotPath != null)
|
||||
_ = Task.Run(async () => { while (!_done) { if (_host.IsWaiting && _host.Pages < _shotPage) _host.SignalInput(); await Task.Delay(1); } });
|
||||
// --shot-sequence: auto-advance past every input wait so the paced burst isn't blocked on a click.
|
||||
if (_seqDir != null)
|
||||
_ = Task.Run(async () => { while (!_done) { if (_host.IsWaiting) _host.SignalInput(); await Task.Delay(1); } });
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
_lastDelta = delta;
|
||||
if (!_selftest && _vm != null) Recomposite(); // retained per-frame compositor (surface+object model)
|
||||
// --shot-sequence: dump one PNG per frame across the opening so a time-based (paced) effect can be
|
||||
// verified as distinct frames, not just the final state. Captures after Recomposite; quits when full.
|
||||
if (_seqDir != null && _seqIdx < _seqFrames && !_done)
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(_seqDir);
|
||||
var fimg = GetViewport().GetTexture().GetImage();
|
||||
fimg.SavePng($"{_seqDir}/frame_{_seqIdx:0000}.png");
|
||||
_seqIdx++;
|
||||
if (_seqIdx >= _seqFrames) { GD.Print($"SEQ saved {_seqIdx} frames -> {_seqDir}"); GetTree().Quit(0); }
|
||||
return;
|
||||
}
|
||||
// --shot: once the target page is composed and parked at wait-for-input, settle a few frames then grab it.
|
||||
if (_shotPath != null && !_shotDone && (_host.Pages >= _shotPage && _host.IsWaiting || _done))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user