Add two-stage charged-crystal demo and original art set.

This commit is contained in:
2026-08-13 13:18:41 +08:00
commit e9f5f81f46
686 changed files with 25601 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
extends SceneTree
const RECIPE_PATH := "res://data/recipe-book.json"
const CATALOG_PATH := "res://data/art-catalog.json"
const MAIN_SCRIPT_PATH := "res://src/main.gd"
func _initialize() -> void:
var failures: Array[String] = []
var recipe := _read_json(RECIPE_PATH)
var catalog := _read_json(CATALOG_PATH)
if recipe.is_empty():
failures.append("Could not read recipe book.")
if catalog.is_empty():
failures.append("Could not read art catalog.")
if failures.is_empty():
failures.append_array(_check_items(recipe, catalog))
failures.append_array(_check_machines(recipe, catalog))
failures.append_array(_check_files(catalog))
failures.append_array(_check_main_uses_generated_art())
if failures.is_empty():
print("ART_CATALOG_SMOKE_OK items=%d machines=%d logistics=%d materials=%d" % [
(catalog.get("items", []) as Array).size(),
(catalog.get("machines", []) as Array).size(),
(catalog.get("logistics", []) as Array).size(),
(catalog.get("materials", []) as Array).size(),
])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _check_items(recipe: Dictionary, catalog: Dictionary) -> Array[String]:
var failures: Array[String] = []
var by_id := _index_by_id(catalog.get("items", []))
for item_variant in recipe.get("items", []):
var item: Dictionary = item_variant
var item_id := String(item.get("id", ""))
if not by_id.has(item_id):
failures.append("Recipe item %s has no art catalog entry." % item_id)
continue
var entry: Dictionary = by_id[item_id]
if String(entry.get("icon", "")).is_empty() or String(entry.get("render", "")).is_empty():
failures.append("Art catalog item %s is missing icon or render." % item_id)
return failures
func _check_machines(recipe: Dictionary, catalog: Dictionary) -> Array[String]:
var failures: Array[String] = []
var by_id := _index_by_id(catalog.get("machines", []))
for machine_variant in recipe.get("machines", []):
var machine: Dictionary = machine_variant
var machine_id := String(machine.get("id", ""))
if not by_id.has(machine_id):
failures.append("Recipe machine %s has no art catalog entry." % machine_id)
continue
if String(by_id[machine_id].get("render", "")).is_empty():
failures.append("Art catalog machine %s is missing a render." % machine_id)
return failures
func _check_files(catalog: Dictionary) -> Array[String]:
var failures: Array[String] = []
var paths: Array[String] = []
for item_variant in catalog.get("items", []):
var item: Dictionary = item_variant
paths.append(String(item.get("icon", "")))
paths.append(String(item.get("render", "")))
for machine_variant in catalog.get("machines", []):
paths.append(String((machine_variant as Dictionary).get("render", "")))
for logistics_variant in catalog.get("logistics", []):
paths.append(String((logistics_variant as Dictionary).get("render", "")))
for material_variant in catalog.get("materials", []):
paths.append(String((material_variant as Dictionary).get("albedo", "")))
for path in paths:
if path.is_empty() or not FileAccess.file_exists("res://" + path.trim_prefix("res://")):
failures.append("Missing generated art file: %s" % path)
return failures
func _check_main_uses_generated_art() -> Array[String]:
var failures: Array[String] = []
var file := FileAccess.open(MAIN_SCRIPT_PATH, FileAccess.READ)
if file == null:
failures.append("Could not read main scene script.")
return failures
var source := file.get_as_text()
if source.find("kenney_factory_kit") >= 0:
failures.append("Playable scene still references Kenney Factory Kit models.")
if source.find("assets/generated/renders/") < 0:
failures.append("Playable scene does not load generated renders.")
return failures
func _index_by_id(rows: Array) -> Dictionary:
var lookup := {}
for row_variant in rows:
var row: Dictionary = row_variant
lookup[String(row.get("id", ""))] = row
return lookup
func _read_json(path: String) -> Dictionary:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
return {}
var parsed: Variant = JSON.parse_string(file.get_as_text())
if typeof(parsed) != TYPE_DICTIONARY:
return {}
return parsed
+1
View File
@@ -0,0 +1 @@
uid://b4fydcbgsr7v7
+120
View File
@@ -0,0 +1,120 @@
extends SceneTree
const WorldSimulation = preload("res://src/simulation/world_simulation.gd")
func _initialize() -> void:
var packed := load("res://scenes/main.tscn") as PackedScene
var demo := packed.instantiate()
root.add_child.call_deferred(demo)
await process_frame
await process_frame
demo.call("_start_game")
_build_cells(demo, WorldSimulation.KIND_BELT, [
Vector2i(-5, 0),
Vector2i(-4, 0),
Vector2i(-3, 0),
Vector2i(-2, 0),
Vector2i(0, 0),
Vector2i(2, 0),
Vector2i(3, 0),
Vector2i(4, 0),
Vector2i(5, 0),
])
_build_cells(demo, WorldSimulation.KIND_MANA_PIPE, [
Vector2i(0, 4),
Vector2i(0, 3),
Vector2i(0, 2),
Vector2i(0, 1),
])
var simulation: WorldSimulation = demo.get("simulation")
var building_count_before_overlap := simulation.buildings.size()
demo.set("hovered_cell", Vector2i(-5, 0))
demo.set("hover_is_valid", true)
demo.call("_try_place_selected")
var overlap_was_rejected := simulation.buildings.size() == building_count_before_overlap
demo.call("_try_remove_hovered")
var removal_succeeded := simulation.get_building_at(Vector2i(-5, 0)).is_empty()
_build_cells(demo, WorldSimulation.KIND_BELT, [Vector2i(-5, 0)])
demo.call("_begin_bridge_config")
_click_bridge_cell(demo, Vector2i(-1, 1))
_click_bridge_cell(demo, Vector2i(-2, 0))
_click_bridge_cell(demo, Vector2i(0, 0))
demo.call("_begin_bridge_config")
_click_bridge_cell(demo, Vector2i(1, 1))
_click_bridge_cell(demo, Vector2i(0, 0))
_click_bridge_cell(demo, Vector2i(2, 0))
# Selecting a locked processor must expose inspection fields without
# mutating the running factory.
demo.set("hovered_cell", Vector2i(-1, 1))
demo.set("hover_is_valid", true)
demo.call("_try_place_selected")
var selected_id := int(demo.get("selected_building_id"))
var inspect_before := simulation.state_fingerprint()
var inspect_report: Dictionary = simulation.inspect_building(selected_id)
var inspect_mutated := inspect_before != simulation.state_fingerprint()
for _tick in range(1800):
simulation.step()
var anchor_machine_id := int(demo.get("anchor_machine_id"))
var charger_machine_id := int(demo.get("charger_machine_id"))
var machine: Dictionary = simulation.buildings[anchor_machine_id]
var charger: Dictionary = simulation.buildings[charger_machine_id]
var failures: Array[String] = []
if bool(demo.get("bridge_mode")):
failures.append("Bridge setup did not leave configuration mode.")
if not overlap_was_rejected:
failures.append("Grid placement allowed an overlapping building.")
if not removal_succeeded:
failures.append("Player removal did not clear grid occupancy.")
if not bool(machine["bridge_configured"]):
failures.append("Anchor machine did not retain bridge endpoints.")
if not bool(charger["bridge_configured"]):
failures.append("Charger did not retain bridge endpoints.")
if not bool(machine["mana_powered"]):
failures.append("Player-built mana pipe route did not power the refinery.")
if not bool(charger["mana_powered"]):
failures.append("Player-built mana pipe route did not power the charger.")
if selected_id != anchor_machine_id:
failures.append("Clicking the refinery did not select it for inspection.")
if inspect_report.is_empty() or inspect_report.get("recipe", {}).is_empty():
failures.append("Selection inspection did not return a recipe payload.")
if inspect_mutated:
failures.append("Inspection mutated simulation state.")
if simulation.get_charged_count() < 15:
failures.append("Completed player flow did not sustain charged-crystal output.")
if not simulation.is_demo_complete():
failures.append("Sustained charged-crystal intake did not mark the demo complete.")
if failures.is_empty():
print("DEMO_FLOW_OK charged=%d refine_bridge=%s->%s charger_bridge=%s->%s mana_network=%d" % [
simulation.get_charged_count(),
str(machine["input_cell"]),
str(machine["output_cell"]),
str(charger["input_cell"]),
str(charger["output_cell"]),
int(machine["mana_network"]),
])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _build_cells(demo: Node, kind: StringName, cells: Array[Vector2i]) -> void:
demo.call("_select_tool", kind)
for cell in cells:
demo.set("hovered_cell", cell)
demo.set("hover_is_valid", true)
demo.call("_try_place_selected")
func _click_bridge_cell(demo: Node, cell: Vector2i) -> void:
demo.set("hovered_cell", cell)
demo.set("hover_is_valid", true)
demo.call("_handle_bridge_click")
+1
View File
@@ -0,0 +1 @@
uid://dtptpsgl6k5vb
+231
View File
@@ -0,0 +1,231 @@
extends SceneTree
const WorldSimulation = preload("res://src/simulation/world_simulation.gd")
func _initialize() -> void:
var failures: Array[String] = []
var reports: Dictionary = {}
reports["no_mana"] = _capture_no_mana()
reports["missing_input"] = _capture_missing_input()
reports["waiting_input"] = _capture_waiting_input()
reports["working"] = _capture_working()
reports["outputting"] = _capture_outputting()
reports["blocked_output"] = _capture_blocked_output()
reports["charger_no_mana"] = _capture_charger_no_mana()
for status in [
&"no_mana",
&"missing_input",
&"waiting_input",
&"working",
&"outputting",
&"blocked_output",
]:
if not reports.has(status):
failures.append("Missing inspection capture for %s." % String(status))
continue
var capture: Dictionary = reports[status]
if not bool(capture.get("ok", false)):
failures.append(
"Could not reach status %s (got %s)." % [
String(status),
String(capture.get("status", &"")),
]
)
continue
failures.append_array(_validate_payload(status, capture["report"], capture["unchanged"]))
var charger_capture: Dictionary = reports["charger_no_mana"]
if not bool(charger_capture.get("ok", false)):
failures.append("Charger did not emit a second-stage no_mana status.")
else:
var charger_report: Dictionary = charger_capture["report"]
var recipe: Dictionary = charger_report.get("recipe", {})
if recipe.get("id", &"") != WorldSimulation.RECIPE_CHARGE:
failures.append("Charger inspection did not expose the charge recipe.")
if recipe.get("input_kind", &"") != WorldSimulation.ITEM_REFINED:
failures.append("Charger recipe input was not refined crystal.")
if recipe.get("output_kind", &"") != WorldSimulation.ITEM_CHARGED:
failures.append("Charger recipe output was not charged crystal.")
failures.append_array(
_validate_payload(&"no_mana", charger_report, charger_capture["unchanged"])
)
if failures.is_empty():
print("INSPECTION_SMOKE_OK statuses=%s" % ",".join([
"no_mana",
"missing_input",
"waiting_input",
"working",
"outputting",
"blocked_output",
"charger_no_mana",
]))
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _validate_payload(status: StringName, report: Dictionary, unchanged: bool) -> Array[String]:
var failures: Array[String] = []
if report.is_empty():
failures.append("Inspection returned an empty payload for %s." % String(status))
return failures
for key in ["recipe", "progress", "inventory", "mana", "reason"]:
if not report.has(key):
failures.append("Inspection for %s missing field %s." % [String(status), key])
var recipe: Dictionary = report.get("recipe", {})
if recipe.is_empty() or not recipe.has("id"):
failures.append("Inspection for %s missing recipe id." % String(status))
var progress: Dictionary = report.get("progress", {})
if not progress.has("ratio") or not progress.has("remaining_ticks"):
failures.append("Inspection for %s missing progress fields." % String(status))
var inventory: Dictionary = report.get("inventory", {})
if not inventory.has("input_count") or not inventory.has("output_buffer"):
failures.append("Inspection for %s missing inventory fields." % String(status))
var mana: Dictionary = report.get("mana", {})
if not mana.has("powered") or not mana.has("network"):
failures.append("Inspection for %s missing mana fields." % String(status))
if status != &"working" and String(report.get("reason", "")).is_empty():
failures.append("Inspection for %s had an empty stop reason." % String(status))
if not unchanged:
failures.append("Inspection for %s mutated tick, inventory, or fingerprint." % String(status))
return failures
func _inspect_without_mutation(simulation: WorldSimulation, building_id: int) -> Dictionary:
var before := {
"tick": simulation.tick_index,
"fingerprint": simulation.state_fingerprint(),
"sink": simulation.sink_inventory.duplicate(),
}
var input_count := -1
if simulation.buildings.has(building_id):
input_count = int(simulation.buildings[building_id].get("input_count", -1))
var report := simulation.inspect_building(building_id)
var unchanged: bool = (
int(before["tick"]) == simulation.tick_index
and String(before["fingerprint"]) == simulation.state_fingerprint()
and before["sink"] == simulation.sink_inventory
and (
input_count < 0
or input_count == int(simulation.buildings[building_id].get("input_count", -2))
)
)
return {
"report": report,
"unchanged": unchanged,
"status": report.get("status", &""),
"ok": not report.is_empty(),
}
func _capture_no_mana() -> Dictionary:
var simulation := WorldSimulation.new()
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.step()
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "no_mana"
return capture
func _capture_missing_input() -> Dictionary:
var simulation := WorldSimulation.new()
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 1), 0)
simulation.step()
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "missing_input"
return capture
func _capture_waiting_input() -> Dictionary:
var simulation := WorldSimulation.new()
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 1), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
simulation.step()
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "waiting_input"
return capture
func _capture_working() -> Dictionary:
var simulation := _powered_line()
var machine_id := _find_kind(simulation, WorldSimulation.KIND_MACHINE)
for _tick in range(400):
simulation.step()
if String(simulation.buildings[machine_id]["status"]) == "working":
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "working"
return capture
return {"ok": false, "status": simulation.buildings[machine_id]["status"], "report": {}, "unchanged": true}
func _capture_outputting() -> Dictionary:
var simulation := _powered_line()
var machine_id := _find_kind(simulation, WorldSimulation.KIND_MACHINE)
var source_cell := Vector2i(-3, 0)
for _tick in range(400):
simulation.step()
if int(simulation.buildings[_find_kind(simulation, WorldSimulation.KIND_SOURCE)].get("produced", 0)) >= 1:
break
simulation.remove_building(source_cell)
for _tick in range(400):
simulation.step()
if String(simulation.buildings[machine_id]["status"]) == "outputting":
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "outputting"
return capture
return {"ok": false, "status": simulation.buildings[machine_id]["status"], "report": {}, "unchanged": true}
func _capture_blocked_output() -> Dictionary:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-3, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-2, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 1), 0)
for _tick in range(400):
simulation.step()
if String(simulation.buildings[machine_id]["status"]) == "blocked_output":
var capture := _inspect_without_mutation(simulation, machine_id)
capture["ok"] = String(capture["status"]) == "blocked_output"
return capture
return {"ok": false, "status": simulation.buildings[machine_id]["status"], "report": {}, "unchanged": true}
func _capture_charger_no_mana() -> Dictionary:
var simulation := WorldSimulation.new()
var charger_id := simulation.place_building(WorldSimulation.KIND_CHARGER, Vector2i(2, 0), 0)
simulation.step()
var capture := _inspect_without_mutation(simulation, charger_id)
capture["ok"] = String(capture["status"]) == "no_mana"
return capture
func _powered_line() -> WorldSimulation:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-3, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-2, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 2), 0)
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, 1), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(1, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(2, 0), 0)
simulation.place_building(WorldSimulation.KIND_SINK, Vector2i(3, 0), 0)
return simulation
func _find_kind(simulation: WorldSimulation, kind: StringName) -> int:
for id_variant in simulation.get_building_ids():
var id := int(id_variant)
if simulation.buildings[id]["kind"] == kind:
return id
return -1
+1
View File
@@ -0,0 +1 @@
uid://bemwej038tpgp
+125
View File
@@ -0,0 +1,125 @@
extends SceneTree
const WorldSimulation = preload("res://src/simulation/world_simulation.gd")
func _initialize() -> void:
var first := _run_factory()
var second := _run_factory()
var mana_pause := _run_mana_pause_resume()
var bridge_result := _run_bridge_factory()
var failures: Array[String] = []
if int(first["refined"]) < 20:
failures.append("Expected at least 20 refined crystals, got %d." % int(first["refined"]))
if first["fingerprint"] != second["fingerprint"]:
failures.append("Identical fixed-tick runs produced different fingerprints.")
if int(first["items"]) != int(second["items"]):
failures.append("Identical runs produced different in-flight item counts.")
if not bool(mana_pause["stopped_without_mana"]):
failures.append("Machine produced output without a connected mana network.")
if not bool(mana_pause["paused_exactly"]):
failures.append("Disconnecting mana did not preserve the unfinished recipe.")
if not bool(mana_pause["resumed"]):
failures.append("Machine did not resume its unfinished recipe after mana reconnection.")
if int(bridge_result["refined"]) < 20:
failures.append("Bridge-bound machine did not sustain production.")
if failures.is_empty():
print("SIMULATION_SMOKE_OK refined=%d fingerprint=%s items=%d" % [
int(first["refined"]),
String(first["fingerprint"]),
int(first["items"]),
])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _run_factory() -> Dictionary:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-3, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-2, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 2), 0)
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, 1), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(1, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(2, 0), 0)
simulation.place_building(WorldSimulation.KIND_SINK, Vector2i(3, 0), 0)
for _tick in range(1800):
simulation.step()
return {
"refined": simulation.get_refined_count(),
"fingerprint": simulation.state_fingerprint(),
"items": simulation.items.size(),
}
func _run_mana_pause_resume() -> Dictionary:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-3, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-2, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(1, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(2, 0), 0)
simulation.place_building(WorldSimulation.KIND_SINK, Vector2i(3, 0), 0)
for _tick in range(200):
simulation.step()
var stopped_without_mana: bool = (
simulation.get_refined_count() == 0
and simulation.buildings[machine_id]["status"] == &"no_mana"
)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 2), 0)
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, 1), 0)
for _tick in range(10):
simulation.step()
var remaining_before_disconnect: int = int(simulation.buildings[machine_id]["completion_tick"]) - simulation.tick_index
simulation.remove_building(Vector2i(0, 1))
var paused_remaining: int = int(simulation.buildings[machine_id]["remaining_ticks"])
for _tick in range(100):
simulation.step()
var paused_exactly: bool = (
simulation.get_refined_count() == 0
and paused_remaining == remaining_before_disconnect
and int(simulation.buildings[machine_id]["completion_tick"]) == -1
)
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, 1), 0)
for _tick in range(120):
simulation.step()
return {
"stopped_without_mana": stopped_without_mana,
"paused_exactly": paused_exactly,
"resumed": simulation.get_refined_count() > 0,
}
func _run_bridge_factory() -> Dictionary:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-3, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-2, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(-1, 0), 0)
var machine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(0, 1), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(1, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(2, 0), 0)
simulation.place_building(WorldSimulation.KIND_SINK, Vector2i(3, 0), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 3), 0)
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, 2), 0)
var configured := simulation.configure_machine_bridge(
machine_id,
Vector2i(-1, 0),
Vector2i(1, 0)
)
if not configured:
return {"refined": 0}
for _tick in range(1800):
simulation.step()
return {"refined": simulation.get_refined_count()}
+1
View File
@@ -0,0 +1 @@
uid://c0ik3wd2ycn1h
+182
View File
@@ -0,0 +1,182 @@
extends SceneTree
const WorldSimulation = preload("res://src/simulation/world_simulation.gd")
const RUN_TICKS := 2000
func _initialize() -> void:
var first := _run_two_stage(RUN_TICKS)
var second := _run_two_stage(RUN_TICKS)
var first_item := _run_until_first_charged()
var blocked := _run_blocked_output()
var failures: Array[String] = []
if int(first["charged"]) <= 0:
failures.append("Two-stage factory produced no charged crystals.")
if int(first["charged"]) != int(second["charged"]):
failures.append("Identical two-stage runs produced different charged counts.")
if String(first["fingerprint"]) != String(second["fingerprint"]):
failures.append("Identical two-stage runs produced different fingerprints.")
if int(first["mass"]) != int(first["produced"]):
failures.append(
"Mass was not conserved: produced=%d mass=%d." % [
int(first["produced"]),
int(first["mass"]),
]
)
if int(second["mass"]) != int(second["produced"]):
failures.append("Second two-stage run did not conserve mass.")
if bool(first_item["saw_item"]) == false:
failures.append("Factory never delivered the first charged crystal.")
if bool(first_item["complete_after_one"]):
failures.append("Demo completed after a single charged crystal.")
if not bool(first["complete"]):
failures.append("Sustained charged-crystal intake did not complete the demo.")
if bool(first_item["complete_before_any"]):
failures.append("Demo reported complete before any charged crystal arrived.")
if not bool(blocked["stopped_upstream"]):
failures.append("Blocked charger output did not stop the upstream refinery.")
if int(blocked["mass"]) != int(blocked["produced"]):
failures.append(
"Blocked-output mass was not conserved: produced=%d mass=%d." % [
int(blocked["produced"]),
int(blocked["mass"]),
]
)
if int(blocked["mass_after"]) != int(blocked["mass"]):
failures.append("Blocked factory lost or duplicated items while idle.")
if int(blocked["produced_after"]) != int(blocked["produced"]):
failures.append("Blocked factory kept spawning items after belts filled.")
if int(blocked["charged"]) != 0:
failures.append("Blocked charger leaked charged crystals into the sink.")
if int(first["charged"]) <= int(blocked["charged"]):
failures.append("Clearing the output-belt bottleneck did not increase charged throughput.")
if failures.is_empty():
print("TWO_STAGE_SMOKE_OK charged=%d fingerprint=%s produced=%d mass=%d" % [
int(first["charged"]),
String(first["fingerprint"]),
int(first["produced"]),
int(first["mass"]),
])
quit(0)
else:
for failure in failures:
push_error(failure)
quit(1)
func _run_two_stage(ticks: int) -> Dictionary:
var simulation := _make_two_stage_factory(true)
for _tick in range(ticks):
simulation.step()
return {
"charged": simulation.get_charged_count(),
"refined": simulation.get_refined_count(),
"fingerprint": simulation.state_fingerprint(),
"produced": _count_produced(simulation),
"mass": _count_mass(simulation),
"complete": simulation.is_demo_complete(),
}
func _run_until_first_charged() -> Dictionary:
var simulation := _make_two_stage_factory(true)
var complete_before_any := simulation.is_demo_complete()
var saw_item := false
var complete_after_one := false
for _tick in range(RUN_TICKS):
simulation.step()
if simulation.get_charged_count() >= 1:
saw_item = true
complete_after_one = simulation.is_demo_complete()
break
return {
"saw_item": saw_item,
"complete_after_one": complete_after_one,
"complete_before_any": complete_before_any,
}
func _run_blocked_output() -> Dictionary:
var simulation := _make_two_stage_factory(false)
for _tick in range(RUN_TICKS):
simulation.step()
var charger_id := _find_kind(simulation, WorldSimulation.KIND_CHARGER)
var refine_id := _find_kind(simulation, WorldSimulation.KIND_MACHINE)
var charger: Dictionary = simulation.buildings[charger_id]
var refine: Dictionary = simulation.buildings[refine_id]
var stopped_upstream := (
String(charger["status"]) == "blocked_output"
and String(refine["status"]) in ["blocked_output", "working", "outputting"]
)
# After the mid belt and charger buffers fill, further ticks must not
# create or destroy crystals.
var mass := _count_mass(simulation)
var produced := _count_produced(simulation)
for _tick in range(200):
simulation.step()
return {
"stopped_upstream": stopped_upstream,
"mass": mass,
"produced": produced,
"mass_after": _count_mass(simulation),
"produced_after": _count_produced(simulation),
"charged": simulation.get_charged_count(),
"charger_status": charger["status"],
"refine_status": refine["status"],
}
func _make_two_stage_factory(with_output_belts: bool) -> WorldSimulation:
var simulation := WorldSimulation.new()
simulation.place_building(WorldSimulation.KIND_SOURCE, Vector2i(-6, 0), 0)
for x in range(-5, -1):
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(x, 0), 0)
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(0, 0), 0)
# A single drop belt with no sink creates real backpressure: the charger
# can emit one charged crystal, then the cell stays occupied.
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(2, 0), 0)
if with_output_belts:
for x in range(3, 6):
simulation.place_building(WorldSimulation.KIND_BELT, Vector2i(x, 0), 0)
simulation.place_building(WorldSimulation.KIND_SINK, Vector2i(6, 0), 0)
var refine_id := simulation.place_building(WorldSimulation.KIND_MACHINE, Vector2i(-1, 1), 0)
var charger_id := simulation.place_building(WorldSimulation.KIND_CHARGER, Vector2i(1, 1), 0)
simulation.place_building(WorldSimulation.KIND_MANA_SOURCE, Vector2i(0, 5), 1)
for z in range(1, 5):
simulation.place_building(WorldSimulation.KIND_MANA_PIPE, Vector2i(0, z), 1)
simulation.configure_machine_bridge(refine_id, Vector2i(-2, 0), Vector2i(0, 0))
simulation.configure_machine_bridge(charger_id, Vector2i(0, 0), Vector2i(2, 0))
return simulation
func _count_produced(simulation: WorldSimulation) -> int:
var produced := 0
for building: Dictionary in simulation.buildings.values():
if building["kind"] == WorldSimulation.KIND_SOURCE:
produced += int(building.get("produced", 0))
return produced
func _count_mass(simulation: WorldSimulation) -> int:
var mass := simulation.items.size()
for kind in simulation.sink_inventory.keys():
mass += int(simulation.sink_inventory[kind])
for building: Dictionary in simulation.buildings.values():
if not WorldSimulation.is_processor(building["kind"]):
continue
mass += int(building.get("input_count", 0))
mass += int(building.get("output_buffer", 0))
if int(building.get("completion_tick", -1)) >= 0 or int(building.get("remaining_ticks", 0)) > 0:
mass += 1
return mass
func _find_kind(simulation: WorldSimulation, kind: StringName) -> int:
for id_variant in simulation.get_building_ids():
var id := int(id_variant)
if simulation.buildings[id]["kind"] == kind:
return id
return -1
+1
View File
@@ -0,0 +1 @@
uid://bpl287yrytxky