Linked repository source
examples/cavern_lab/live/project/bake/bake_process.gd
This read-only view comes from the file in the Gododin repository. It retains its design, example or evidence context.
Download the original file · Return to the guide
extends SceneTree
# The Phase 14 headless bake child (bake/README.md). It runs as
# `godot --headless --script res://bake/bake_process.gd -- <request> <output>
# <digest>` inside the staged project, loads the current accepted native image
# through the standard GODODIN_IMAGE_* runtime contract, recomputes the request
# validates the result, and publishes the destination asset with an atomic
# temp-file rename. Exit codes: 0 publish, 1 bake failure, 3 no usable image.
const DIGEST := preload("res://bake/bake_digest.gd")
const DATA_SCRIPT := preload("res://bake/cave_bake_data.gd")
const MIN_DIMENSION := 8
const MAX_DIMENSION := 1024
func _initialize() -> void:
_run()
func _fail(stage: String, reason: String) -> void:
print("GODODIN_BAKE_FAILED stage=%s reason=%s" % [stage, reason])
quit(1)
func _run() -> void:
var arguments := OS.get_cmdline_user_args()
if arguments.size() != 3:
return _fail("arguments", "expected request output digest")
var request_path: String = arguments[0]
var output_path: String = arguments[1]
var expected_digest: String = arguments[2]
if OS.get_environment("GODODIN_IMAGE_PATH").is_empty() \
or OS.get_environment("GODODIN_IMAGE_SHA256").is_empty():
print("GODODIN_BAKE_NOIMAGE reason=image_environment_missing")
quit(3)
return
# Resolve the native class through ClassDB so this script parses (and the
# no-image refusal stays reachable) even when no image is registered.
if not ClassDB.class_exists("CavernGenerator"):
print("GODODIN_BAKE_NOIMAGE reason=native_class_unavailable")
quit(3)
return
var request = load(request_path)
if request == null:
return _fail("request", "cannot load %s" % request_path)
var mask_image: Image = request.mask
if mask_image == null:
return _fail("mask", "request carries no mask Image")
if mask_image.get_format() != Image.FORMAT_L8:
return _fail("mask", "mask format %d is not L8" % mask_image.get_format())
var width := mask_image.get_width()
var height := mask_image.get_height()
if width < MIN_DIMENSION or width > MAX_DIMENSION or height < MIN_DIMENSION or height > MAX_DIMENSION:
return _fail("bounds", "mask %dx%d outside %d..%d" % [width, height, MIN_DIMENSION, MAX_DIMENSION])
var mask := mask_image.get_data()
for cell in mask:
if cell != 0 and cell != 255:
return _fail("mask", "byte %d outside 0/255" % cell)
var smoothing := int(request.smoothing_steps)
var minimum_region := int(request.minimum_region_size)
var digest := DIGEST.digest(mask, width, height, smoothing, minimum_region)
if digest != expected_digest:
return _fail("digest", "shell and child digests differ")
var native = ClassDB.instantiate("CavernGenerator")
root.add_child(native)
var processed: PackedByteArray = native.process_mask(mask, width, height, smoothing, minimum_region)
if processed.size() != width * height:
return _fail("kernel", "rejected with last_error=%d" % int(native.get_last_error()))
var floor_cells := 0
for y in height:
for x in width:
var cell := processed[y * width + x]
if cell != 0 and cell != 255:
return _fail("validate", "byte %d outside 0/255" % cell)
if x == 0 or y == 0 or x == width - 1 or y == height - 1:
if cell != 255:
return _fail("validate", "border cell %d,%d is not a wall" % [x, y])
elif cell == 0:
floor_cells += 1
if floor_cells == 0:
return _fail("validate", "no floor remains")
if floor_cells != int(native.get_floor_cells()):
return _fail("validate", "floor count differs from the kernel report")
var data = DATA_SCRIPT.new()
data.width = width
data.height = height
data.grid = processed
data.diagnostics = {
"smoothing_steps": smoothing,
"minimum_region_size": minimum_region,
"final_regions": int(native.get_final_regions()),
"connections": int(native.get_connections()),
"floor_cells": floor_cells,
"result_hash": int(native.get_result_hash()),
"generation_usec": int(native.get_generation_usec()),
}
data.request_digest = digest
data.native_revision = OS.get_environment("GODODIN_IMAGE_SHA256")
# The temporary publication keeps a .tres extension: ResourceSaver selects
# its writer by extension, and rename within the directory stays atomic.
var temporary := output_path.get_basename() + ".bake-tmp.tres"
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_path.get_base_dir()))
DirAccess.remove_absolute(ProjectSettings.globalize_path(temporary))
var save_error := ResourceSaver.save(data, temporary)
if save_error != OK:
return _fail("publish", "temporary save error %d" % save_error)
var rename_error := DirAccess.rename_absolute(
ProjectSettings.globalize_path(temporary),
ProjectSettings.globalize_path(output_path))
if rename_error != OK:
return _fail("publish", "rename error %d" % rename_error)
print("GODODIN_BAKE_PUBLISH output=%s width=%d height=%d floor_cells=%d result_hash=%d digest=%s revision=%s" % [
output_path, width, height, floor_cells, int(native.get_result_hash()), digest, data.native_revision])
quit(0)