3fb45f0ef2
checkpoint / checkpoint (push) Successful in 1m47s
Now that cloudbison can render (Radeon GPU via Xvfb, or llvmpipe software), added scripts/tools/screenshot_rig.gd + scenes/dev/ so a scene can be booted, run a few frames, and its rendered frame saved to PNG -- caught three things the headless smoke never could: - limbs floated detached from the torso: widened the torso and overlapped the arm/leg boxes into it, and pulled limb depth in behind the torso front so they stop z-fighting. - proportions were scrawny: reworked to Roblox R6 ratios (2-stud torso, 2-stud arms/legs, 1-stud head) with the head clearly pegged on top. - the walk swing was a 40-deg lunge: STRIDE 0.7->0.42 rad, separate 0.32 arm swing, KNEE 1.0->0.5. Reads as a walk now, not a crouch. test_level lighting was near-black on the shaded side (ambient was disabled) -- colour ambient at 0.8 + a stronger key light, so the avatar reads in-world. avatar_preview.tscn (AGP_PREVIEW_WALK=1) renders the assembled avatar walking in place for quick visual checks. Verified: avatar smoke 14/14 source + exported pack; fresh-clone boot PASS; eyeballed in rendered screenshots (game scene + walk sequence). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
73 lines
2.6 KiB
GDScript
73 lines
2.6 KiB
GDScript
extends Node
|
|
## Dev-only screenshot harness. Boots a target scene inside the real (GPU)
|
|
## renderer, lets it run a few frames, then reads the rendered frame back off
|
|
## the viewport and writes it to disk. Nothing in the shipped game touches this.
|
|
##
|
|
## Driven entirely by environment variables so it needs no CLI parsing:
|
|
## AGP_SHOT_SCENE res:// path of the scene to capture (default: game.tscn)
|
|
## AGP_SHOT_OUT output PNG path (absolute) (default: /tmp/agp_shot.png)
|
|
## AGP_SHOT_FRAMES frames to settle before the first shot (default: 45)
|
|
## AGP_SHOT_SEQ "count:gap" -> take <count> shots <gap> frames apart,
|
|
## suffixing the filename _01.._NN (default: single shot)
|
|
##
|
|
## Launch (hardware Vulkan on cloudbison's Radeon, via Xvfb):
|
|
## Xvfb :99 -screen 0 1280x720x24 &
|
|
## sg render -c "sg video -c 'DISPLAY=:99 AGP_SHOT_SCENE=res://scenes/game.tscn \
|
|
## AGP_SHOT_OUT=/abs/shot.png ~/godot/godot --path . \
|
|
## --rendering-driver vulkan --gpu-index 0 --resolution 1280x720 \
|
|
## res://scenes/dev/screenshot_rig.tscn'"
|
|
|
|
func _ready() -> void:
|
|
_run.call_deferred()
|
|
|
|
|
|
func _run() -> void:
|
|
var target := OS.get_environment("AGP_SHOT_SCENE")
|
|
if target.is_empty():
|
|
target = "res://scenes/game.tscn"
|
|
var out := OS.get_environment("AGP_SHOT_OUT")
|
|
if out.is_empty():
|
|
out = "/tmp/agp_shot.png"
|
|
var settle := int(OS.get_environment("AGP_SHOT_FRAMES"))
|
|
if settle <= 0:
|
|
settle = 45
|
|
|
|
var seq_count := 1
|
|
var seq_gap := 0
|
|
var seq_env := OS.get_environment("AGP_SHOT_SEQ")
|
|
if seq_env.contains(":"):
|
|
var parts := seq_env.split(":")
|
|
seq_count = maxi(1, int(parts[0]))
|
|
seq_gap = maxi(1, int(parts[1]))
|
|
|
|
var packed: PackedScene = load(target)
|
|
if packed == null:
|
|
push_error("screenshot_rig: cannot load %s" % target)
|
|
get_tree().quit(1)
|
|
return
|
|
var inst: Node = packed.instantiate()
|
|
get_tree().root.add_child(inst)
|
|
get_tree().current_scene = inst
|
|
|
|
for i in settle:
|
|
await get_tree().process_frame
|
|
|
|
for shot in seq_count:
|
|
# get_image() on the viewport texture is a GPU->CPU readback of the
|
|
# frame that was just rendered -- no window presentation needed, which
|
|
# is why this works over Xvfb (no DRI3).
|
|
var img: Image = get_viewport().get_texture().get_image()
|
|
var path := out
|
|
if seq_count > 1:
|
|
path = "%s_%02d.png" % [out.get_basename(), shot + 1]
|
|
var err := img.save_png(path)
|
|
if err != OK:
|
|
push_error("screenshot_rig: save_png(%s) failed: %d" % [path, err])
|
|
else:
|
|
print("SHOT %s (%dx%d)" % [path, img.get_width(), img.get_height()])
|
|
if shot < seq_count - 1:
|
|
for j in seq_gap:
|
|
await get_tree().process_frame
|
|
|
|
get_tree().quit()
|