P14-02: Split warps into guest FRONT and staff BACK service warp (A08)
The FRONT Mana warp is guest-only: its connectors carry the "!staff" access, which path access checks treat as open to everyone except staff. A new 2x2 BACK service warp links only to other service warps for staff, with A07 values. Registry and palette now hold 25 definitions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
619b5e07ed
commit
8917c190cf
@@ -8363,7 +8363,7 @@ func _apply_completed_facility_internal(instance: Dictionary, absolute_minute: i
|
|||||||
var transport := _synchronize_elevators(absolute_minute)
|
var transport := _synchronize_elevators(absolute_minute)
|
||||||
if not bool(transport.get(&"ok", false)):
|
if not bool(transport.get(&"ok", false)):
|
||||||
return {&"ok": false, &"code": &"ELEVATOR_SYNC_FAILED", &"reason": elevators.last_error, &"details": transport}
|
return {&"ok": false, &"code": &"ELEVATOR_SYNC_FAILED", &"reason": elevators.last_error, &"details": transport}
|
||||||
if definition.id == WarpService.DEFINITION_ID:
|
if definition.id in WarpService.DEFINITION_IDS:
|
||||||
_register_facility_mana_consumer(instance)
|
_register_facility_mana_consumer(instance)
|
||||||
var warp_sync := _synchronize_warps()
|
var warp_sync := _synchronize_warps()
|
||||||
if not bool(warp_sync.get(&"ok", false)):
|
if not bool(warp_sync.get(&"ok", false)):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
script = ExtResource("1")
|
script = ExtResource("1")
|
||||||
id = &"mana_warp"
|
id = &"mana_warp"
|
||||||
display_name = "マナワープ"
|
display_name = "マナワープ"
|
||||||
description = "建設済みの全ワープと瞬時につながる表層の転移陣。客と接客中の従業員が1回3マナで移動する。"
|
description = "建設済みの表層ワープ同士を瞬時につなぐ客用の転移陣。客が1回3マナで移動する。従業員は裏層の業務用ワープを使う。"
|
||||||
zone = 0
|
zone = 0
|
||||||
layer = 0
|
layer = 0
|
||||||
footprint = Vector2i(2, 2)
|
footprint = Vector2i(2, 2)
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
[gd_resource type="Resource" script_class="FacilityDefinition" load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scripts/data/facility_definition.gd" id="1"]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
id = &"service_warp"
|
||||||
|
display_name = "業務用マナワープ"
|
||||||
|
description = "建設済みの業務用ワープ同士を瞬時につなぐ裏層の転移陣。従業員が1回3マナで移動する。"
|
||||||
|
zone = 0
|
||||||
|
layer = 1
|
||||||
|
footprint = Vector2i(2, 2)
|
||||||
|
construction_cost = {&"gold": 3000, &"materials": 30}
|
||||||
|
build_minutes = 360
|
||||||
|
weight_per_cell = 3
|
||||||
|
pass_through = true
|
||||||
|
maintenance_gold = 15
|
||||||
|
maintenance_mana = 1
|
||||||
|
power_priority = 80
|
||||||
|
staff_min = 0
|
||||||
|
staff_max = 0
|
||||||
|
capacity = 4
|
||||||
|
queue_capacity = 0
|
||||||
|
base_service_minutes = 1
|
||||||
|
base_quality = 50
|
||||||
|
base_price = 0
|
||||||
|
tags = Array[StringName]([&"movement", &"schema_facility_proxy", &"staff_only", &"warp"])
|
||||||
@@ -48,6 +48,14 @@
|
|||||||
"decision": "2x2・表層。建設3000G・資材30、工期360分、維持費15G/日、維持マナ1/10分。建設済みワープは全て相互接続。利用者は客と接客中の従業員。1人1回3マナを出発時に一回だけ差し引く。残量不足・停電・到着先不可のときは経路から除外し、費用と移動を発生させない。途中保存・再開で費用と移動を二重反映しない。",
|
"decision": "2x2・表層。建設3000G・資材30、工期360分、維持費15G/日、維持マナ1/10分。建設済みワープは全て相互接続。利用者は客と接客中の従業員。1人1回3マナを出発時に一回だけ差し引く。残量不足・停電・到着先不可のときは経路から除外し、費用と移動を発生させない。途中保存・再開で費用と移動を二重反映しない。",
|
||||||
"origin": "user_reply_in_conversation",
|
"origin": "user_reply_in_conversation",
|
||||||
"approved_at_jst": "2026-10-20"
|
"approved_at_jst": "2026-10-20"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "A08",
|
||||||
|
"title": "ワープの客用・業務用分離",
|
||||||
|
"decision": "表層のマナワープは客用とし、従業員の経路からは使わない。従業員用に裏層の業務用マナワープ(2x2・裏層)を別に設け、業務用同士だけを相互接続し従業員のみ利用する。業務用の建設費・工期・維持費・維持マナ・1人3マナの運賃はA07と同値。",
|
||||||
|
"origin": "user_reply_in_conversation「従業員用を裏層に別で作って」",
|
||||||
|
"approved_at_jst": "2026-10-20",
|
||||||
|
"values_note": "業務用の数値はユーザー指定がなくA07と同値を適用。"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"pending": [
|
"pending": [
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
"passed": true
|
"passed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "all7_confirmed_decisions",
|
"name": "all8_confirmed_decisions",
|
||||||
"passed": true
|
"passed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
"build_atomic_requirements.py": "6d6ea9e4216372dbd0f9fed3f885a4481b21a3f539a26f64cf98a6830af984c5",
|
"build_atomic_requirements.py": "6d6ea9e4216372dbd0f9fed3f885a4481b21a3f539a26f64cf98a6830af984c5",
|
||||||
"build_facility_value_contracts.py": "35027b23ce75ce9e4e0cdfc5132fd2eb771ba9f598d97750d7ecf9ecb257f769",
|
"build_facility_value_contracts.py": "35027b23ce75ce9e4e0cdfc5132fd2eb771ba9f598d97750d7ecf9ecb257f769",
|
||||||
"current_evidence_links.json": "db1837bcc8497201134297e312f4b266a297102285307ec3075d4e23632e26c2",
|
"current_evidence_links.json": "db1837bcc8497201134297e312f4b266a297102285307ec3075d4e23632e26c2",
|
||||||
"decisions.json": "b0e4483e453703d0d5587ba340a2ab7f61cb95b34bd9cd1f1a411b1b8f52c866",
|
"decisions.json": "8b7dba984d5b1cc0091e8faf8fddb2ab357dd79f1bd093eba46c23c2f62a068c",
|
||||||
"facility_value_contracts.json": "447388ba03a29bfac25f2bcd068665aedbb07ba7641ce86f2db93d44f4c9f326",
|
"facility_value_contracts.json": "447388ba03a29bfac25f2bcd068665aedbb07ba7641ce86f2db93d44f4c9f326",
|
||||||
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
|
"facility_value_contracts_validation.json": "bf490a9ca585c48fc318e76c4a069ef244827d828d46e6415399f7710590dd16",
|
||||||
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
|
"performance_audit.json": "0b630b15a392c3e0366f1362bfe4ddacb968f915f54549f82e4d6fa315384762",
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
"source_atomic_coverage.json": "2ab7b9e6a2536ef248d4f7ccd695bf75a082b47018aca7a5a1b6b706fe1907bc",
|
"source_atomic_coverage.json": "2ab7b9e6a2536ef248d4f7ccd695bf75a082b47018aca7a5a1b6b706fe1907bc",
|
||||||
"validate_atomic_requirements.py": "5b2d0b7d72cb124830fbb0a12d1434e6db698148cc5263d33fd5bd7c2953a1e0",
|
"validate_atomic_requirements.py": "5b2d0b7d72cb124830fbb0a12d1434e6db698148cc5263d33fd5bd7c2953a1e0",
|
||||||
"validate_facility_value_contracts.py": "91640e2a76cd6cc9186f7b85c0bf379b24b6cac0d48962815235ee628e7ae43e",
|
"validate_facility_value_contracts.py": "91640e2a76cd6cc9186f7b85c0bf379b24b6cac0d48962815235ee628e7ae43e",
|
||||||
"validate_plan.py": "13d6c95ec17e87215cefc3a94f398b10dd9b63516cd0039bc6e77c3940998d84",
|
"validate_plan.py": "4973d3c2797289d266f5c6e7c36f3d2c31931b23a1022eab3d94328267bcc1b4",
|
||||||
"validator_update_checks.json": "f997d0f3de2cea29550840b1d5cb23647d64f81329de46758551e2b6c8fecc27",
|
"validator_update_checks.json": "f997d0f3de2cea29550840b1d5cb23647d64f81329de46758551e2b6c8fecc27",
|
||||||
"work_packages.json": "d2549dae70c8240cef62c5447d570d67789a46f8b944422b5155d326aab13984"
|
"work_packages.json": "d2549dae70c8240cef62c5447d570d67789a46f8b944422b5155d326aab13984"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ def main():
|
|||||||
pending = {d["id"] for d in decisions["pending"]}
|
pending = {d["id"] for d in decisions["pending"]}
|
||||||
check("all10_pending_decision_groups", pending == {f"D{i:02d}" for i in range(1, 11)})
|
check("all10_pending_decision_groups", pending == {f"D{i:02d}" for i in range(1, 11)})
|
||||||
check("known_decision_references", all(set(p["decisions"]) <= pending for p in packages))
|
check("known_decision_references", all(set(p["decisions"]) <= pending for p in packages))
|
||||||
check("all7_confirmed_decisions", {d["id"] for d in decisions["confirmed"]} == {f"A{i:02d}" for i in range(1, 8)})
|
check("all8_confirmed_decisions", {d["id"] for d in decisions["confirmed"]} == {f"A{i:02d}" for i in range(1, 9)})
|
||||||
check("confirmed_decision_references_exist", all((PLAN / d["reference"]).is_file() for d in decisions["confirmed"] if "reference" in d))
|
check("confirmed_decision_references_exist", all((PLAN / d["reference"]).is_file() for d in decisions["confirmed"] if "reference" in d))
|
||||||
state_counts = dict(collections.Counter(p["state"] for p in packages))
|
state_counts = dict(collections.Counter(p["state"] for p in packages))
|
||||||
check("valid_package_states", all(p["state"] in states for p in packages))
|
check("valid_package_states", all(p["state"] in states for p in packages))
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ const MVP_FACILITY_IDS: Array[StringName] = [
|
|||||||
&"guest_stairs",
|
&"guest_stairs",
|
||||||
&"service_stairs",
|
&"service_stairs",
|
||||||
&"mana_warp",
|
&"mana_warp",
|
||||||
|
&"service_warp",
|
||||||
&"staff_door",
|
&"staff_door",
|
||||||
&"guest_corridor",
|
&"guest_corridor",
|
||||||
&"service_corridor",
|
&"service_corridor",
|
||||||
|
|||||||
@@ -67,8 +67,11 @@ const INFRASTRUCTURE_FACILITY_PATHS: Array[String] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
## A07 Mana warp: original 10.7 transport, kept apart from the stair list.
|
## A07 Mana warp: original 10.7 transport, kept apart from the stair list.
|
||||||
const TRANSPORT_FACILITY_IDS: Array[StringName] = [&"mana_warp"]
|
const TRANSPORT_FACILITY_IDS: Array[StringName] = [&"mana_warp", &"service_warp"]
|
||||||
const TRANSPORT_FACILITY_PATHS: Array[String] = ["res://data/facilities/infrastructure/mana_warp.tres"]
|
const TRANSPORT_FACILITY_PATHS: Array[String] = [
|
||||||
|
"res://data/facilities/infrastructure/mana_warp.tres",
|
||||||
|
"res://data/facilities/infrastructure/service_warp.tres",
|
||||||
|
]
|
||||||
|
|
||||||
## Optional post-MVP expansion definitions. They live below a subdirectory so
|
## Optional post-MVP expansion definitions. They live below a subdirectory so
|
||||||
## DefinitionRegistry's baseline directory scan remains exactly compatible with
|
## DefinitionRegistry's baseline directory scan remains exactly compatible with
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
## A07 Mana warp: every completed warp is linked to every other warp by one
|
## A07/A08 Mana warp: every completed warp is linked to every other warp of the
|
||||||
## path connector, so crossing is a single instant step. Connectors are derived
|
## same kind by one path connector, so crossing is a single instant step. The
|
||||||
## from construction state; their operational state follows the reserve and the
|
## FRONT guest warp excludes staff routes; the BACK service warp is staff-only.
|
||||||
## warps' own power. Fares are charged by the Runtime when an actor steps.
|
## Connectors are derived from construction state; their operational state
|
||||||
|
## follows the reserve and the warps' own power. The Runtime charges fares.
|
||||||
const PathType := preload("res://scripts/world/path_service.gd")
|
const PathType := preload("res://scripts/world/path_service.gd")
|
||||||
const DEFINITION_ID := &"mana_warp"
|
const DEFINITION_ID := &"mana_warp"
|
||||||
|
const SERVICE_DEFINITION_ID := &"service_warp"
|
||||||
|
const DEFINITION_IDS: Array[StringName] = [DEFINITION_ID, SERVICE_DEFINITION_ID]
|
||||||
|
const ACCESS_BY_DEFINITION := {&"mana_warp": &"!staff", &"service_warp": &"staff"}
|
||||||
const PREFIX := "warp.connector:"
|
const PREFIX := "warp.connector:"
|
||||||
const MANA_PER_TRIP := 3
|
const MANA_PER_TRIP := 3
|
||||||
const PATH_COST := 400
|
const PATH_COST := 400
|
||||||
@@ -22,12 +26,14 @@ static func warps_from_facilities(facilities: Array) -> Array[Dictionary]:
|
|||||||
if not value is Dictionary:
|
if not value is Dictionary:
|
||||||
continue
|
continue
|
||||||
var facility: Dictionary = value
|
var facility: Dictionary = value
|
||||||
if StringName(str(facility.get(&"definition_id", ""))) != DEFINITION_ID:
|
var definition_id := StringName(str(facility.get(&"definition_id", "")))
|
||||||
|
if definition_id not in DEFINITION_IDS:
|
||||||
continue
|
continue
|
||||||
var origin: Variant = facility.get(&"origin")
|
var origin: Variant = facility.get(&"origin")
|
||||||
if not origin is Vector3i:
|
if not origin is Vector3i:
|
||||||
continue
|
continue
|
||||||
warps.append({&"instance_id": StringName(str(facility.get(&"instance_id", ""))), &"origin": origin})
|
warps.append({&"instance_id": StringName(str(facility.get(&"instance_id", ""))), &"origin": origin,
|
||||||
|
&"definition_id": definition_id})
|
||||||
warps.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
warps.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
|
||||||
return String(a[&"instance_id"]) < String(b[&"instance_id"]))
|
return String(a[&"instance_id"]) < String(b[&"instance_id"]))
|
||||||
return warps
|
return warps
|
||||||
@@ -38,7 +44,10 @@ static func expected_connectors(facilities: Array) -> Array[Dictionary]:
|
|||||||
var result: Array[Dictionary] = []
|
var result: Array[Dictionary] = []
|
||||||
for i: int in warps.size():
|
for i: int in warps.size():
|
||||||
for j: int in range(i + 1, warps.size()):
|
for j: int in range(i + 1, warps.size()):
|
||||||
|
if warps[i][&"definition_id"] != warps[j][&"definition_id"]:
|
||||||
|
continue
|
||||||
result.append({
|
result.append({
|
||||||
|
&"access_id": ACCESS_BY_DEFINITION[warps[i][&"definition_id"]],
|
||||||
&"id": StringName("%s%s|%s" % [PREFIX, warps[i][&"instance_id"], warps[j][&"instance_id"]]),
|
&"id": StringName("%s%s|%s" % [PREFIX, warps[i][&"instance_id"], warps[j][&"instance_id"]]),
|
||||||
&"from": warps[i][&"origin"],
|
&"from": warps[i][&"origin"],
|
||||||
&"to": warps[j][&"origin"],
|
&"to": warps[j][&"origin"],
|
||||||
@@ -71,14 +80,15 @@ static func synchronize(path: PathService, facilities: Array, available: Diction
|
|||||||
var usable := fare_affordable and bool(available.get(endpoints[0], false)) and bool(available.get(endpoints[1], false))
|
var usable := fare_affordable and bool(available.get(endpoints[0], false)) and bool(available.get(endpoints[1], false))
|
||||||
var state := PathType.CONNECTOR_OPERATIONAL if usable else PathType.CONNECTOR_OUTAGE
|
var state := PathType.CONNECTOR_OPERATIONAL if usable else PathType.CONNECTOR_OUTAGE
|
||||||
if not existing.is_empty() and existing[&"from"] == connector[&"from"] and existing[&"to"] == connector[&"to"] \
|
if not existing.is_empty() and existing[&"from"] == connector[&"from"] and existing[&"to"] == connector[&"to"] \
|
||||||
and existing[&"operational_state"] == state:
|
and existing[&"access_id"] == connector[&"access_id"] and existing[&"operational_state"] == state:
|
||||||
continue
|
continue
|
||||||
if before == null:
|
if before == null:
|
||||||
before = path.create_snapshot()
|
before = path.create_snapshot()
|
||||||
if existing.is_empty() or existing[&"from"] != connector[&"from"] or existing[&"to"] != connector[&"to"]:
|
if existing.is_empty() or existing[&"from"] != connector[&"from"] or existing[&"to"] != connector[&"to"] \
|
||||||
|
or existing[&"access_id"] != connector[&"access_id"]:
|
||||||
if not existing.is_empty():
|
if not existing.is_empty():
|
||||||
path.remove_connector(connector_id)
|
path.remove_connector(connector_id)
|
||||||
if not path.configure_connector(connector_id, connector[&"from"], connector[&"to"], PATH_COST, true, &"", true,
|
if not path.configure_connector(connector_id, connector[&"from"], connector[&"to"], PATH_COST, true, connector[&"access_id"], true,
|
||||||
{&"movement_type": PathType.MOVEMENT_CONNECTOR}):
|
{&"movement_type": PathType.MOVEMENT_CONNECTOR}):
|
||||||
path.restore_snapshot(before)
|
path.restore_snapshot(before)
|
||||||
return {&"ok": false, &"code": &"WARP_CONNECTOR_CONFIGURE_FAILED", &"reason": path.last_error}
|
return {&"ok": false, &"code": &"WARP_CONNECTOR_CONFIGURE_FAILED", &"reason": path.last_error}
|
||||||
@@ -97,7 +107,7 @@ static func connectors_match(path: PathService, facilities: Array) -> bool:
|
|||||||
for connector: Dictionary in expected:
|
for connector: Dictionary in expected:
|
||||||
var existing := path.get_connector(connector[&"id"])
|
var existing := path.get_connector(connector[&"id"])
|
||||||
if existing.is_empty() or existing[&"from"] != connector[&"from"] or existing[&"to"] != connector[&"to"] \
|
if existing.is_empty() or existing[&"from"] != connector[&"from"] or existing[&"to"] != connector[&"to"] \
|
||||||
or int(existing[&"cost"]) != PATH_COST or StringName(existing[&"access_id"]) != &"" \
|
or int(existing[&"cost"]) != PATH_COST or StringName(existing[&"access_id"]) != connector[&"access_id"] \
|
||||||
or existing[&"movement_type"] != PathType.MOVEMENT_CONNECTOR:
|
or existing[&"movement_type"] != PathType.MOVEMENT_CONNECTOR:
|
||||||
return false
|
return false
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ static func room(canvas: CanvasItem, rect: Rect2, id: StringName, pixel: float,
|
|||||||
table(canvas, at + Vector2(-2, -7)*p, p)
|
table(canvas, at + Vector2(-2, -7)*p, p)
|
||||||
&"chapel":
|
&"chapel":
|
||||||
chapel_furnishings(canvas, rect, p)
|
chapel_furnishings(canvas, rect, p)
|
||||||
&"mana_warp":
|
&"mana_warp", &"service_warp":
|
||||||
# Original code-drawn transfer circle and rising motes; existing palette.
|
# Original code-drawn transfer circle and rising motes; existing palette.
|
||||||
var ring := Vector2(mid, floor_y - 3.0 * p)
|
var ring := Vector2(mid, floor_y - 3.0 * p)
|
||||||
canvas.draw_circle(ring, 9.0 * p, INK)
|
canvas.draw_circle(ring, 9.0 * p, INK)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const KIND_SERVICE_ELEVATOR: StringName = &"service_elevator"
|
|||||||
const KIND_GUEST_STAIRS: StringName = &"guest_stairs"
|
const KIND_GUEST_STAIRS: StringName = &"guest_stairs"
|
||||||
const KIND_SERVICE_STAIRS: StringName = &"service_stairs"
|
const KIND_SERVICE_STAIRS: StringName = &"service_stairs"
|
||||||
const KIND_MANA_WARP: StringName = &"mana_warp"
|
const KIND_MANA_WARP: StringName = &"mana_warp"
|
||||||
|
const KIND_SERVICE_WARP: StringName = &"service_warp"
|
||||||
const SUPPORTED_KINDS: Array[StringName] = [
|
const SUPPORTED_KINDS: Array[StringName] = [
|
||||||
KIND_GUEST_CORRIDOR,
|
KIND_GUEST_CORRIDOR,
|
||||||
KIND_SERVICE_CORRIDOR,
|
KIND_SERVICE_CORRIDOR,
|
||||||
@@ -45,6 +46,7 @@ const SUPPORTED_KINDS: Array[StringName] = [
|
|||||||
KIND_GUEST_STAIRS,
|
KIND_GUEST_STAIRS,
|
||||||
KIND_SERVICE_STAIRS,
|
KIND_SERVICE_STAIRS,
|
||||||
KIND_MANA_WARP,
|
KIND_MANA_WARP,
|
||||||
|
KIND_SERVICE_WARP,
|
||||||
]
|
]
|
||||||
const KNOWN_KINDS: Array[StringName] = [
|
const KNOWN_KINDS: Array[StringName] = [
|
||||||
KIND_GUEST_CORRIDOR,
|
KIND_GUEST_CORRIDOR,
|
||||||
@@ -55,6 +57,7 @@ const KNOWN_KINDS: Array[StringName] = [
|
|||||||
KIND_GUEST_STAIRS,
|
KIND_GUEST_STAIRS,
|
||||||
KIND_SERVICE_STAIRS,
|
KIND_SERVICE_STAIRS,
|
||||||
KIND_MANA_WARP,
|
KIND_MANA_WARP,
|
||||||
|
KIND_SERVICE_WARP,
|
||||||
]
|
]
|
||||||
|
|
||||||
const UNSUPPORTED_VERTICAL_KINDS: Array[StringName] = []
|
const UNSUPPORTED_VERTICAL_KINDS: Array[StringName] = []
|
||||||
@@ -706,6 +709,16 @@ static func _kind_schema(kind: StringName) -> Dictionary:
|
|||||||
&"guest_access", &"movement", &"schema_facility_proxy", &"warp",
|
&"guest_access", &"movement", &"schema_facility_proxy", &"warp",
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
KIND_SERVICE_WARP:
|
||||||
|
return {
|
||||||
|
&"layer": PathType.BACK,
|
||||||
|
&"footprint": Vector2i(2, 2),
|
||||||
|
&"pass_through": true,
|
||||||
|
&"staff_only": true,
|
||||||
|
&"tags": _sorted_name_array([
|
||||||
|
&"movement", &"schema_facility_proxy", &"staff_only", &"warp",
|
||||||
|
]),
|
||||||
|
}
|
||||||
KIND_SERVICE_ELEVATOR:
|
KIND_SERVICE_ELEVATOR:
|
||||||
return {
|
return {
|
||||||
&"layer": PathType.BACK,
|
&"layer": PathType.BACK,
|
||||||
|
|||||||
@@ -2099,7 +2099,11 @@ func _edge_meets_vertical_policy(from_cell: Vector3i, edge: Dictionary) -> bool:
|
|||||||
|
|
||||||
func _edge_is_accessible(edge: Dictionary, allowed_access_ids: Array[StringName]) -> bool:
|
func _edge_is_accessible(edge: Dictionary, allowed_access_ids: Array[StringName]) -> bool:
|
||||||
var required_access: StringName = edge[&"access_id"]
|
var required_access: StringName = edge[&"access_id"]
|
||||||
if not required_access.is_empty() and required_access not in allowed_access_ids:
|
# "!id" is open to everyone except actors that carry id (guest-only warp).
|
||||||
|
if String(required_access).begins_with("!"):
|
||||||
|
if StringName(String(required_access).substr(1)) in allowed_access_ids:
|
||||||
|
return false
|
||||||
|
elif not required_access.is_empty() and required_access not in allowed_access_ids:
|
||||||
return false
|
return false
|
||||||
var connector_id: StringName = edge[&"connector_id"]
|
var connector_id: StringName = edge[&"connector_id"]
|
||||||
if connector_id.is_empty():
|
if connector_id.is_empty():
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func _test_tick_command_execution() -> void:
|
|||||||
|
|
||||||
func _test_starter_world_layers() -> void:
|
func _test_starter_world_layers() -> void:
|
||||||
_reset_runtime()
|
_reset_runtime()
|
||||||
_expect(_runtime.definition_registry.facility_count() == 24, "Runtime loads baseline sixteen plus stairs, the infirmary, premium room/dormitory, chapel and A07 warp")
|
_expect(_runtime.definition_registry.facility_count() == 25, "Runtime loads baseline sixteen plus stairs, the infirmary, premium room/dormitory, chapel and A07/A08 warps")
|
||||||
_expect(_runtime.definition_registry.has_facility(&"chapel"), "Runtime exposes the original chapel definition")
|
_expect(_runtime.definition_registry.has_facility(&"chapel"), "Runtime exposes the original chapel definition")
|
||||||
var front_start := Vector3i(-2, 0, WorldService.LAYER_FRONT)
|
var front_start := Vector3i(-2, 0, WorldService.LAYER_FRONT)
|
||||||
var back_goal := Vector3i(2, 0, WorldService.LAYER_BACK)
|
var back_goal := Vector3i(2, 0, WorldService.LAYER_BACK)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func _run() -> void:
|
|||||||
_state = load("res://autoload/game_state.gd").new()
|
_state = load("res://autoload/game_state.gd").new()
|
||||||
_reset()
|
_reset()
|
||||||
_expect(_runtime.path_service.get_vertical_walk_requires_connector(), "new Runtime requires explicit vertical transport")
|
_expect(_runtime.path_service.get_vertical_walk_requires_connector(), "new Runtime requires explicit vertical transport")
|
||||||
_expect(_runtime.definition_registry.facility_count() == 24, "Runtime registers baseline16, stairs, infirmary, premium room/dormitory, chapel and A07 warp")
|
_expect(_runtime.definition_registry.facility_count() == 25, "Runtime registers baseline16, stairs, infirmary, premium room/dormitory, chapel and A07/A08 warps")
|
||||||
var before: Dictionary = _runtime.create_snapshot()
|
var before: Dictionary = _runtime.create_snapshot()
|
||||||
var invalid := _command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(4, 3, 0)})
|
var invalid := _command(&"build_facility", {&"definition_id": &"guest_stairs", &"origin": Vector3i(4, 3, 0)})
|
||||||
_expect(not invalid.is_success() and invalid.details.get(&"reason") == &"STAIR_LANDING_MISSING", "stairs without a lower landing reject before any construction")
|
_expect(not invalid.is_success() and invalid.details.get(&"reason") == &"STAIR_LANDING_MISSING", "stairs without a lower landing reject before any construction")
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func _warp_events() -> Array[Dictionary]:
|
|||||||
|
|
||||||
|
|
||||||
func _write() -> void:
|
func _write() -> void:
|
||||||
_expect(_cmd(&"change_resources", {&"deltas": {&"gold": 6000, &"materials": 40}}).is_success(), "fund two warps for the fixture")
|
_expect(_cmd(&"change_resources", {&"deltas": {&"gold": 12000, &"materials": 100}}).is_success(), "fund four warps for the fixture")
|
||||||
var gold_before: int = _state.get_resource(&"gold")
|
var gold_before: int = _state.get_resource(&"gold")
|
||||||
if not _build(&"mana_warp", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)):
|
if not _build(&"mana_warp", Vector3i(0, 0, 0)) or not _build(&"reception", Vector3i(2, 0, 0)):
|
||||||
return
|
return
|
||||||
@@ -86,6 +86,17 @@ func _write() -> void:
|
|||||||
_expect(connector[&"from"] == Vector3i(0, 0, 0) and connector[&"to"] == Vector3i(0, 2, 0) and connector[&"operational_state"] == PathService.CONNECTOR_OPERATIONAL, "connector joins both warp origins and is operational")
|
_expect(connector[&"from"] == Vector3i(0, 0, 0) and connector[&"to"] == Vector3i(0, 2, 0) and connector[&"operational_state"] == PathService.CONNECTOR_OPERATIONAL, "connector joins both warp origins and is operational")
|
||||||
_expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)) == [Vector3i(0, 0, 0), Vector3i(0, 2, 0)], "path crosses floors in one instant step")
|
_expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0)) == [Vector3i(0, 0, 0), Vector3i(0, 2, 0)], "path crosses floors in one instant step")
|
||||||
_expect(_runtime.mana_network_service.get_total_consumer_demand() >= 2, "each warp registers 1 Mana upkeep")
|
_expect(_runtime.mana_network_service.get_total_consumer_demand() >= 2, "each warp registers 1 Mana upkeep")
|
||||||
|
_expect(_runtime.path_service.find_path(Vector3i(0, 0, 0), Vector3i(0, 2, 0), [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"]).is_empty(), "staff routes never use the guest warp (A08)")
|
||||||
|
_expect(_build(&"service_warp", Vector3i(0, 0, 1)), "build lower BACK service warp")
|
||||||
|
_clock.advance_minutes(360)
|
||||||
|
_expect(_build(&"service_warp", Vector3i(0, 2, 1)), "build upper BACK service warp on the completed one")
|
||||||
|
_clock.advance_minutes(360)
|
||||||
|
_clock.advance_minutes(10 - int(_clock.absolute_minute) % 10)
|
||||||
|
_expect(_warp_connectors().size() == 2, "service warps link only to each other, never to guest warps")
|
||||||
|
var staff_route: Array = _runtime.path_service.find_path(Vector3i(0, 0, 1), Vector3i(0, 2, 1), [WorldService.LAYER_FRONT, WorldService.LAYER_BACK], [&"staff"])
|
||||||
|
_expect(staff_route == [Vector3i(0, 0, 1), Vector3i(0, 2, 1)], "staff cross floors through the BACK service warp")
|
||||||
|
_expect(_runtime.path_service.find_path(Vector3i(0, 0, 1), Vector3i(0, 2, 1)).is_empty(), "guests cannot use the service warp")
|
||||||
|
_expect(_runtime.call(&"_warp_step_connector", Vector3i(0, 0, 1), Vector3i(0, 2, 1), [&"staff"] as Array[StringName]) != &"", "a staff step through the service warp is fare-bearing")
|
||||||
|
|
||||||
# Outage and an unaffordable fare both remove the warp from routing.
|
# Outage and an unaffordable fare both remove the warp from routing.
|
||||||
var before_outage: Dictionary = _runtime.create_snapshot()
|
var before_outage: Dictionary = _runtime.create_snapshot()
|
||||||
|
|||||||
@@ -131,8 +131,8 @@ func _run() -> void:
|
|||||||
)
|
)
|
||||||
await _test_defense_notice()
|
await _test_defense_notice()
|
||||||
var palette := _main.find_child("BuildPalette", true, false) as GridContainer
|
var palette := _main.find_child("BuildPalette", true, false) as GridContainer
|
||||||
_expect(palette.get_child_count() == 24, "all24 facilities including the cleaning room and A07 warp appear in the build palette")
|
_expect(palette.get_child_count() == 25, "all25 facilities including the cleaning room and both A07/A08 warps appear in the build palette")
|
||||||
_expect((_main.get("build_buttons") as Dictionary).has(&"mana_warp"), "Mana warp is buildable from the ordinary palette")
|
_expect((_main.get("build_buttons") as Dictionary).has(&"mana_warp") and (_main.get("build_buttons") as Dictionary).has(&"service_warp"), "guest and service Mana warps are buildable from the ordinary palette")
|
||||||
var iconized_build_buttons := 0
|
var iconized_build_buttons := 0
|
||||||
for build_button_value: Variant in (_main.get("build_buttons") as Dictionary).values():
|
for build_button_value: Variant in (_main.get("build_buttons") as Dictionary).values():
|
||||||
if (build_button_value as Button).icon != null:
|
if (build_button_value as Button).icon != null:
|
||||||
|
|||||||
Reference in New Issue
Block a user