Files
Papay-Shooter/debug/anime_mecha_capture.gd
2026-08-09 01:31:44 -04:00

167 lines
5.7 KiB
GDScript

extends SceneTree
## Rendered character-art gate for the focused Sakura second-skin pilot.
## Captures open/deploying/closed helmet states and verifies that active jet
## flames emerge from all four integrated vector-jet mouths.
var _scene: Node3D
var _camera: Camera3D
var _models: Array[SkinnedPlayerModel] = []
var _jets: Array[PlayerJetVFX] = []
var _out_dir := "."
func _initialize() -> void:
var args := OS.get_cmdline_user_args()
if not args.is_empty():
_out_dir = args[0]
if not _out_dir.is_absolute_path():
_out_dir = ProjectSettings.globalize_path(_out_dir)
DirAccess.make_dir_recursive_absolute(_out_dir)
_run()
func _run() -> void:
_scene = Node3D.new()
root.add_child(_scene)
current_scene = _scene
_build_studio()
_add_pilot("default", "sakura",
"res://assets/characters/skins/sakura_pilot_hd.glb", 0.0)
for _i in 24:
await process_frame
for model in _models:
model.update_state("ground", 0.0, false)
model.set_locomotion(0.0, 1.0, 0.0)
_camera.global_position = Vector3(0.0, 1.05, -3.2)
_camera.look_at(Vector3(0.0, 0.92, 0.0), Vector3.UP)
await _capture("pilot_open")
_camera.global_position = Vector3(0.0, 1.58, -1.15)
_camera.look_at(Vector3(0.0, 1.57, 0.0), Vector3.UP)
await _capture("helmet_open_closeup")
_models[0].set_helmet_closed(true)
for _i in 5:
await process_frame
await _capture("pilot_helmet_deploy")
for _i in 40:
await process_frame
await _capture("pilot_helmet_closed")
_camera.global_position = Vector3(0.0, 1.08, 3.2)
_camera.look_at(Vector3(0.0, 0.92, 0.0), Vector3.UP)
await _capture("pilot_rear")
for jet in _jets:
jet.burst_double_jump()
for _i in 4:
await process_frame
await _capture("pilot_thrusters")
_models[0].set_wall_side(-1.0)
_models[0].set_wall_glide_motion(Vector3.RIGHT * 13.0)
for _i in 24:
_models[0].update_state("wall_run", 13.0, false)
_jets[0].set_wall_glide(true, Vector2.RIGHT)
await process_frame
_camera.global_position = Vector3(2.4, 1.45, -2.7)
_camera.look_at(Vector3(0.0, 1.0, 0.0), Vector3.UP)
await _capture("pilot_wall_glide")
var failed := false
if not _models[0].helmet_closed_debug() \
or _models[0].helmet_progress_debug() < 0.98:
push_error("ANIME_MECHA_CAPTURE helmet failed to finish deployment")
failed = true
for model_index in _models.size():
var model := _models[model_index]
var resolved_roles: Array[String] = []
var missing_roles: Array[String] = []
for role in ["hips", "shoulder.L", "shoulder.R", "upper_arm.L",
"upper_arm.R", "forearm.L", "forearm.R", "thigh.L",
"thigh.R", "shin.L", "shin.R", "foot.L", "foot.R"]:
if model._role_bone(role, []) >= 0:
resolved_roles.append(role)
else:
missing_roles.append(role)
print("ANIME_MECHA_CAPTURE %s: %d detail parts, %d nozzles, " % [
model.skin_id, model.mecha_detail_part_count_debug(),
model.mecha_nozzle_count_debug()], "resolved=", resolved_roles,
" missing=", missing_roles)
if not missing_roles.is_empty():
var bone_names: Array[String] = []
if model.skeleton:
for bone_index in model.skeleton.get_bone_count():
bone_names.append(model.skeleton.get_bone_name(bone_index))
print("ANIME_MECHA_CAPTURE unresolved sidecar roles=",
model._rig_info.get("roles", {}), " skeleton bones=", bone_names)
if model.mecha_detail_part_count_debug() < 180:
push_error("ANIME_MECHA_CAPTURE insufficient hardware detail on %s: %d parts" % [
model.skin_id, model.mecha_detail_part_count_debug()])
failed = true
if model.mecha_nozzle_count_debug() != 4:
push_error("ANIME_MECHA_CAPTURE missing four visible nozzles on %s" % model.skin_id)
failed = true
var nozzles := model.get_jet_socket_world_positions()
var flames := _jets[model_index].get_flame_world_positions()
if nozzles.size() != 4 or flames.size() != 4:
push_error("ANIME_MECHA_CAPTURE socket count mismatch on %s" % model.skin_id)
failed = true
continue
for nozzle_index in 4:
if nozzles[nozzle_index].distance_to(flames[nozzle_index]) > 0.001:
push_error("ANIME_MECHA_CAPTURE flame %d misses %s nozzle" % [
nozzle_index, model.skin_id])
failed = true
print("ANIME_MECHA_CAPTURE %s" % ("FAIL" if failed else "PASS"))
quit(1 if failed else 0)
func _build_studio() -> void:
var world := WorldEnvironment.new()
var environment := Environment.new()
environment.background_mode = Environment.BG_COLOR
environment.background_color = Color("b8c0cc")
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
environment.ambient_light_color = Color("e8edf6")
environment.ambient_light_energy = 1.15
environment.tonemap_mode = Environment.TONE_MAPPER_FILMIC
world.environment = environment
_scene.add_child(world)
var sun := DirectionalLight3D.new()
sun.rotation_degrees = Vector3(-42.0, -28.0, 0.0)
sun.light_energy = 1.9
_scene.add_child(sun)
_camera = Camera3D.new()
_camera.fov = 38.0
_scene.add_child(_camera)
_camera.current = true
func _add_pilot(id: String, theme: String, path: String, x: float) -> void:
var model := SkinnedPlayerModel.new()
model.skin_id = id
model.mecha_theme = theme
model.model_path = path
model.position.x = x
_scene.add_child(model)
# Static art QA does not need the expensive full-quality hair/cloth solver;
# gameplay and the picker still use their normal distance-driven LOD.
if model._spring_mod:
model._spring_mod.lod = 3
model._spring_mod = null
_models.append(model)
var jet := PlayerJetVFX.new()
jet.name = "JetVFX_%s" % id
_scene.add_child(jet)
jet.set_visual_model(model)
jet.set_render_enabled(true)
_jets.append(jet)
func _capture(tag: String) -> void:
for _i in 5:
await process_frame
var path := _out_dir.path_join(tag + ".png")
root.get_texture().get_image().save_png(path)
print("ANIME_MECHA_CAPTURE saved ", path)