Gododin Gododin Documentation

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
# digest, runs the pure kernel's process_mask through the authored class,
# 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)