Save The Tree implementation and acceptance work in progress

Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
This commit is contained in:
Takeshi
2026-10-11 15:58:00 +09:00
parent 39f6068461
commit b84bade045
235 changed files with 495831 additions and 312 deletions
+85 -5
View File
@@ -145,6 +145,12 @@ func set_operational_status(
var related := StringName(raw_related)
if related == instance_id or (not related.is_empty() and not has_instance(related)):
return _invalid("Related facility is missing or refers to itself.")
if instance.requires_operating_kitchen() and not related.is_empty():
var kitchen := get_instance(related)
if kitchen.definition_id != &"kitchen" or kitchen.origin.z != WorldService.LAYER_BACK:
return _invalid("Dining facilities require one existing BACK kitchen.", {&"reason": &"INVALID_RELATED_KITCHEN"})
if not _required_related_placement_matches(instance, kitchen):
return _invalid("The BACK kitchen must lie inside this FRONT dining facility's XY footprint.", {&"reason": &"INVALID_RELATED_KITCHEN_PLACEMENT"})
var seen := {instance_id: true}
var cursor := related
while not cursor.is_empty() and has_instance(cursor):
@@ -253,7 +259,7 @@ func create_request(
)
func enqueue_request(request: Variant, absolute_minute: int = -1) -> CommandResult:
func enqueue_request(request: Variant, absolute_minute: int = -1, commit_clock: bool = true) -> CommandResult:
if request == null or not request is ServiceRequest:
return _invalid("Service request is missing or has an invalid type.")
var queued_request: ServiceRequest = request
@@ -269,7 +275,11 @@ func enqueue_request(request: Variant, absolute_minute: int = -1) -> CommandResu
"Facility queue is full.",
{&"instance_id": instance.instance_id, &"queue_capacity": instance.queue_capacity}
)
var minute := _resolve_minute(absolute_minute)
# Queue registration can precede the Clock increment in APPLY_COMMANDS.
# Preserve the request effective time without claiming participant advancement.
if not commit_clock and absolute_minute != current_minute + 1:
return _invalid("Deferred queue registration must target the next uncommitted minute.")
var minute := _resolve_minute(absolute_minute) if commit_clock else absolute_minute
queued_request.enqueued_at = minute
_requests[queued_request.request_id] = queued_request
_known_request_ids[queued_request.request_id] = true
@@ -342,6 +352,76 @@ func cancel_request(request_id: StringName, reason: StringName = &"GUEST_LEFT")
return _not_found("Queued service request", request_id)
func _required_related_kind_matches(instance: FacilityInstance) -> bool:
if not instance.requires_operating_kitchen():
return true
var kitchen := get_instance(instance.related_facility_id)
return kitchen != null and kitchen.definition_id == &"kitchen" and kitchen.origin.z == WorldService.LAYER_BACK
# Original 2.2 and 8.3 put a BACK kitchen behind the FRONT dining venue.
# XY containment is Root's operational interpretation, not a width/origin-x
# equality rule. Use actual occupied cells; do not synthesize missing geometry,
# rewrite old IDs, or persist this derived predicate.
func _required_related_placement_matches(instance: FacilityInstance, proposed_kitchen: FacilityInstance = null) -> bool:
if not instance.requires_operating_kitchen():
return true
var kitchen: FacilityInstance = proposed_kitchen if proposed_kitchen != null else get_instance(instance.related_facility_id)
if kitchen == null or kitchen.definition_id != &"kitchen" \
or instance.origin.z != WorldService.LAYER_FRONT or kitchen.origin.z != WorldService.LAYER_BACK \
or instance.occupied_cells.is_empty() or kitchen.occupied_cells.is_empty():
return false
var front_xy: Dictionary = {}
for cell: Vector3i in instance.occupied_cells:
if cell.z != WorldService.LAYER_FRONT:
return false
front_xy[Vector2i(cell.x, cell.y)] = true
for cell: Vector3i in kitchen.occupied_cells:
if cell.z != WorldService.LAYER_BACK or not front_xy.has(Vector2i(cell.x, cell.y)):
return false
return true
# Read-only derived eligibility. Legacy cached state/IDs remain untouched until
# actual gameplay evaluates a facility or advances one minute.
func get_required_kitchen_report(instance_id: StringName) -> Dictionary:
var instance := get_instance(instance_id)
if instance == null:
return {}
var required := instance.requires_operating_kitchen()
var related := instance.related_facility_id
var kitchen := get_instance(related)
var valid_kind := _required_related_kind_matches(instance)
var valid_placement := _required_related_placement_matches(instance)
var operating := not required or (valid_placement and _restored_dependency_operates(related, {}))
var reason: StringName = &""
if required:
if related.is_empty(): reason = &"KITCHEN_NOT_LINKED"
elif kitchen == null: reason = &"KITCHEN_REMOVED"
elif not valid_kind: reason = &"INVALID_RELATED_KITCHEN"
elif not valid_placement: reason = &"INVALID_RELATED_KITCHEN_PLACEMENT"
elif not kitchen.suspension_reason.is_empty(): reason = &"KITCHEN_SUSPENDED"
elif not operating: reason = &"KITCHEN_INACTIVE"
return {&"required": required, &"related_facility_id": related, &"exists": kitchen != null, &"valid_kind": valid_kind, &"operating": operating, &"reason": reason}
# Read-only eligibility of a proposed link. Does not select a kitchen, alter an
# existing related ID, evaluate state, advance time, or clear persistent suspension.
func get_kitchen_placement_report(instance_id: StringName, kitchen_id: StringName) -> Dictionary:
var instance := get_instance(instance_id)
var kitchen := get_instance(kitchen_id)
var required := instance != null and instance.requires_operating_kitchen()
var valid_kind := kitchen != null and kitchen.definition_id == &"kitchen" and kitchen.origin.z == WorldService.LAYER_BACK
var valid_placement := required and valid_kind and _required_related_placement_matches(instance, kitchen)
var reason: StringName = &""
if instance == null: reason = &"FACILITY_NOT_FOUND"
elif not required: reason = &"KITCHEN_NOT_REQUIRED"
elif kitchen == null: reason = &"KITCHEN_REMOVED"
elif not valid_kind: reason = &"INVALID_RELATED_KITCHEN"
elif not valid_placement: reason = &"INVALID_RELATED_KITCHEN_PLACEMENT"
return {&"facility_id": instance_id, &"kitchen_id": kitchen_id, &"required": required, &"exists": kitchen != null, &"valid_kind": valid_kind, &"valid_placement": valid_placement, &"reason": reason}
func refresh_operating_dependencies(minute: int) -> void:
var resolved: Dictionary = {}
for instance_id: StringName in _sorted_keys(_instances):
@@ -357,7 +437,7 @@ func _resolve_operating_dependency(id: StringName, minute: int, resolved: Dictio
var instance := get_instance(id)
instance.apply_pending_policy(minute)
var related := instance.related_facility_id
instance.related_facility_operating = related.is_empty() or _resolve_operating_dependency(related, minute, resolved, visiting)
instance.related_facility_operating = _required_related_placement_matches(instance) and (related.is_empty() or _resolve_operating_dependency(related, minute, resolved, visiting))
var old_state := instance.state
instance.evaluate_state(minute)
_emit_state_change(instance, old_state)
@@ -686,7 +766,7 @@ func restore_snapshot(snapshot: Dictionary) -> bool:
_next_service_sequence = restored_next_sequence
current_minute = restored_minute
for instance: FacilityInstance in _instances.values():
instance.related_facility_operating = _restored_dependency_operates(instance.related_facility_id, {})
instance.related_facility_operating = _required_related_placement_matches(instance) and _restored_dependency_operates(instance.related_facility_id, {})
instance.operation_state = FacilityInstanceType.OperationState.SUSPENDED if not instance.suspension_reason.is_empty() else (FacilityInstanceType.OperationState.OPERATING if instance.state in [FacilityInstanceType.State.OPEN, FacilityInstanceType.State.DEGRADED] and instance.related_facility_operating else FacilityInstanceType.OperationState.INACTIVE)
last_error = ""
return true
@@ -699,7 +779,7 @@ func _restored_dependency_operates(id: StringName, seen: Dictionary) -> bool:
return false
seen[id] = true
var instance := get_instance(id)
return instance.suspension_reason.is_empty() and instance.state in [FacilityInstanceType.State.OPEN, FacilityInstanceType.State.DEGRADED] and _restored_dependency_operates(instance.related_facility_id, seen)
return _required_related_placement_matches(instance) and instance.suspension_reason.is_empty() and instance.state in [FacilityInstanceType.State.OPEN, FacilityInstanceType.State.DEGRADED] and _restored_dependency_operates(instance.related_facility_id, seen)
func _saved_policy_is_valid(policy: Dictionary) -> bool: