Preserve unadopted integer window codec candidate and static review checkpoint
This commit is contained in:
+15
@@ -0,0 +1,15 @@
|
||||
# P23-02 整数サンプル符号化候補 r285-r02 開発チェックポイント
|
||||
|
||||
2026-10-12時点の作業をGitで保持するためのスナップショット。制作本体に未採用の候補であり、P23-02の正式完了ではない。台帳の基準はコミット `21d89b988d360269321d79a23f011858777e2fbf`(r282)。30パッケージ/88成果単位、正式9/着手19/予定60、部分点なしを維持する。
|
||||
|
||||
整数Arrayの従来の符号化を、元エンジン形式と照合する処理と追記・単一先頭削除の変換に置き換える実験候補。形式不一致時の従来処理を残す。`source/`は候補、`parent-r274/`は比較元、`history-r285/`は静的レビューで拒否された初版。初版の一時ベクトル確保失敗への対処不足を、r285-r02で二段階の読み取りと確保結果確認へ修正した。
|
||||
|
||||
- r285-r02は独立静的レビュー受理済み。ビルド・ロード・エンジンとの実バイト同値・性能は未検証。
|
||||
- r286のビルド事前レビューは受理済み。bridge compile1+link1だけが対象で、ビルド自体は未実行。実行前の証拠再照合とroot verificationが必要。
|
||||
- 次はr286を必要な事前確認後に実行し、実ビルドの独立レビューを行う。その後、既存のBudget局所比較を再利用してエンジンcodecとの同値・入力と所有関係・通知と記録の維持を検証する。
|
||||
- 最新の局所・原S6影響・whole647受理済み候補はr274/library275。r285-r02へその受入や性能値を転用しない。
|
||||
- 原17条件C01–C09/X1–X8はすべて未受入。Windows実機を要するC08/X8だけブロック。人のCtrl保持wheel/左保持dragは延期・未達・免除なし。50ms、P95/D10、同一最終版の必須全回帰、通常OS操作と同一Windows配布版実機等が残る。
|
||||
|
||||
`manifest.json`はコピー元パスとサイズ・SHA256を保存する。コピーした証拠とinput内の絶対パスは、当時のローカル環境に対する原記録として変更していない。`preparation/`は当時の準備スクリプトで、ローカルの既存artifact、SDK133、所有プロセス用helper等に依存する。このディレクトリだけでビルド・受入を再現できるという認定はない。SDK、バイナリ、代表セーブ、巨大ログ、生成stageはこのコミットの対象外。
|
||||
|
||||
`reference/marshalls.cpp`はGodotのリビジョン `ed1daf0bf` にある `core/io/marshalls.cpp` の原文。配布元URLとハッシュは `reference/source.json` に記録し、ファイル冒頭のMITライセンスと著作権表示を保持する。
|
||||
+287
@@ -0,0 +1,287 @@
|
||||
class_name SimulationPerformanceBudget
|
||||
extends RefCounted
|
||||
|
||||
## Lightweight runtime profiler with explicit integer-microsecond samples.
|
||||
## It is diagnostic state only and deliberately stays outside Save snapshots.
|
||||
|
||||
signal budget_exceeded(metric_id: StringName, report: Dictionary)
|
||||
|
||||
enum Aggregation {
|
||||
AVERAGE,
|
||||
P95,
|
||||
MAXIMUM,
|
||||
}
|
||||
|
||||
const DEFAULT_SAMPLE_CAPACITY := 512
|
||||
|
||||
const DEFAULT_BUDGETS := {
|
||||
&"simulation_x1": {&"aggregation": Aggregation.AVERAGE, &"limit_us": 4_000},
|
||||
&"simulation_x3": {&"aggregation": Aggregation.P95, &"limit_us": 12_000},
|
||||
&"path_query": {&"aggregation": Aggregation.P95, &"limit_us": 2_000},
|
||||
&"main_thread_stall": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 50_000},
|
||||
&"save": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 5_000_000},
|
||||
&"load": {&"aggregation": Aggregation.MAXIMUM, &"limit_us": 8_000_000},
|
||||
}
|
||||
|
||||
var sample_capacity := DEFAULT_SAMPLE_CAPACITY
|
||||
var _p23r257_native_core: RefCounted = null
|
||||
var _budgets: Dictionary = {}
|
||||
var _samples: Dictionary = {}
|
||||
# Derived diagnostic index; never part of game Save snapshots.
|
||||
var _ordered_samples: Dictionary = {}
|
||||
var _ordered_sample_bytes: Dictionary = {}
|
||||
# Complete raw bytes proved integer by this builtin; diagnostic-only derived state.
|
||||
var _integer_window_bytes: Dictionary = {}
|
||||
var _totals: Dictionary = {}
|
||||
var _sample_counts: Dictionary = {}
|
||||
|
||||
|
||||
func _init(capacity := DEFAULT_SAMPLE_CAPACITY) -> void:
|
||||
sample_capacity = maxi(int(capacity), 1)
|
||||
for raw_metric_id: Variant in DEFAULT_BUDGETS:
|
||||
_budgets[StringName(raw_metric_id)] = (
|
||||
DEFAULT_BUDGETS[raw_metric_id] as Dictionary
|
||||
).duplicate(true)
|
||||
|
||||
|
||||
func set_budget(
|
||||
metric_id: StringName,
|
||||
limit_us: int,
|
||||
aggregation: Aggregation = Aggregation.P95
|
||||
) -> bool:
|
||||
if metric_id.is_empty() or limit_us <= 0 \
|
||||
or aggregation < Aggregation.AVERAGE or aggregation > Aggregation.MAXIMUM:
|
||||
return false
|
||||
_budgets[metric_id] = {
|
||||
&"aggregation": aggregation,
|
||||
&"limit_us": limit_us,
|
||||
}
|
||||
return true
|
||||
|
||||
|
||||
func record_sample(metric_id: StringName, duration_us: int) -> Dictionary:
|
||||
if metric_id.is_empty() or duration_us < 0:
|
||||
return {&"ok": false, &"error": "Metric ID and non-negative duration are required."}
|
||||
if get_script().resource_path == "res://scripts/core/performance_budget.gd":
|
||||
if _p23r257_native_core == null and ClassDB.class_exists(&"TheTreeNumericCore"):
|
||||
_p23r257_native_core = ClassDB.instantiate(&"TheTreeNumericCore")
|
||||
if _p23r257_native_core != null and _p23r257_native_core.get_class() == &"TheTreeNumericCore" and _p23r257_native_core.get_script() == null and _p23r257_native_core.has_method(&"performance_record_sample"):
|
||||
var native_result: Dictionary = _p23r257_native_core.call(&"performance_record_sample", self, metric_id, duration_us)
|
||||
if bool(native_result.get(&"supported", false)):
|
||||
var native_report: Dictionary = native_result[&"report"]
|
||||
if bool(native_report.get(&"budget_exceeded", false)):
|
||||
budget_exceeded.emit(metric_id, native_report.duplicate(true))
|
||||
return {&"ok": true, &"report": native_report}
|
||||
if not _samples.has(metric_id):
|
||||
_samples[metric_id] = []
|
||||
var values: Array = _samples[metric_id]
|
||||
var use_index: bool = get_script().resource_path == "res://scripts/core/performance_budget.gd" and _known_integer_window(metric_id, values)
|
||||
var ordered: Array[int] = []
|
||||
if use_index: ordered = _validated_sample_order(metric_id, values)
|
||||
values.append(duration_us)
|
||||
if use_index: ordered.insert(ordered.bsearch(duration_us), duration_us)
|
||||
if values.size() > sample_capacity:
|
||||
var removed: Variant = values.pop_front()
|
||||
if use_index: ordered.remove_at(ordered.bsearch(int(removed)))
|
||||
if use_index:
|
||||
_ordered_sample_bytes[metric_id] = var_to_bytes(values)
|
||||
_integer_window_bytes[metric_id] = _ordered_sample_bytes[metric_id]
|
||||
else:
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
_integer_window_bytes.erase(metric_id)
|
||||
_totals[metric_id] = int(_totals.get(metric_id, 0)) + duration_us
|
||||
_sample_counts[metric_id] = int(_sample_counts.get(metric_id, 0)) + 1
|
||||
var report: Dictionary = _indexed_report(metric_id, values, ordered) if use_index else get_report(metric_id)
|
||||
if bool(report.get(&"budget_exceeded", false)):
|
||||
budget_exceeded.emit(metric_id, report.duplicate(true))
|
||||
return {&"ok": true, &"report": report}
|
||||
|
||||
|
||||
func record_simulation_tick(duration_us: int, speed: int) -> Dictionary:
|
||||
var metric_id := &"simulation_x3" if speed == 3 else &"simulation_x1"
|
||||
return record_sample(metric_id, duration_us)
|
||||
|
||||
|
||||
## Records the full main-thread cost of one rendered/process frame, including
|
||||
## all game-minute ticks consumed at accelerated speed. This keeps x3 from
|
||||
## accidentally reporting four individually-fast ticks while the frame stalls.
|
||||
func record_simulation_frame(duration_us: int, speed: int, ticks_run: int) -> Dictionary:
|
||||
if ticks_run <= 0:
|
||||
return {&"ok": false, &"error": "At least one completed tick is required."}
|
||||
var simulation_result := record_simulation_tick(duration_us, speed)
|
||||
var stall_result := record_sample(&"main_thread_stall", duration_us)
|
||||
return {
|
||||
&"ok": bool(simulation_result[&"ok"]) and bool(stall_result[&"ok"]),
|
||||
&"ticks_run": ticks_run,
|
||||
&"simulation": simulation_result,
|
||||
&"main_thread": stall_result,
|
||||
}
|
||||
|
||||
|
||||
func get_report(metric_id: StringName) -> Dictionary:
|
||||
var raw: Array = _samples.get(metric_id, [])
|
||||
if get_script().resource_path != "res://scripts/core/performance_budget.gd" or not _known_integer_window(metric_id, raw):
|
||||
return SimulationPerformanceBudget._legacy_report(self, metric_id)
|
||||
var ordered: Array[int] = []
|
||||
return _indexed_report(metric_id, raw, ordered, true)
|
||||
|
||||
|
||||
func _indexed_report(metric_id: StringName, values: Array, ordered: Array[int], validate_order: bool = false) -> Dictionary:
|
||||
var count := values.size()
|
||||
var lifetime_count := int(_sample_counts.get(metric_id, 0))
|
||||
var lifetime_total := int(_totals.get(metric_id, 0))
|
||||
var average_us := lifetime_total / lifetime_count if lifetime_count > 0 else 0
|
||||
if validate_order: ordered = _validated_sample_order(metric_id, values)
|
||||
var maximum_us := maxi(0, ordered[-1]) if not ordered.is_empty() else 0
|
||||
var p95_us := 0
|
||||
if not ordered.is_empty():
|
||||
var rank_numerator := 95 * ordered.size()
|
||||
var index := maxi((rank_numerator + 99) / 100 - 1, 0)
|
||||
p95_us = ordered[mini(index, ordered.size() - 1)]
|
||||
var budget: Dictionary = _budgets.get(metric_id, {})
|
||||
var aggregation := int(budget.get(&"aggregation", Aggregation.P95))
|
||||
var measured_us := _aggregated_value(aggregation, average_us, p95_us, maximum_us)
|
||||
var limit_us := int(budget.get(&"limit_us", 0))
|
||||
return {
|
||||
&"metric_id": metric_id,
|
||||
&"window_sample_count": count,
|
||||
&"lifetime_sample_count": lifetime_count,
|
||||
&"average_us": average_us,
|
||||
&"p95_us": p95_us,
|
||||
&"maximum_us": maximum_us,
|
||||
&"aggregation": aggregation,
|
||||
&"measured_us": measured_us,
|
||||
&"limit_us": limit_us,
|
||||
&"budget_exceeded": limit_us > 0 and measured_us > limit_us,
|
||||
}
|
||||
|
||||
|
||||
func get_all_reports() -> Array[Dictionary]:
|
||||
var metric_ids: Array[StringName] = []
|
||||
for raw_metric_id: Variant in _budgets:
|
||||
metric_ids.append(StringName(raw_metric_id))
|
||||
for raw_metric_id: Variant in _samples:
|
||||
var metric_id := StringName(raw_metric_id)
|
||||
if not metric_ids.has(metric_id):
|
||||
metric_ids.append(metric_id)
|
||||
metric_ids.sort_custom(func(a: StringName, b: StringName) -> bool:
|
||||
return String(a) < String(b)
|
||||
)
|
||||
var reports: Array[Dictionary] = []
|
||||
for metric_id: StringName in metric_ids:
|
||||
reports.append(get_report(metric_id))
|
||||
return reports
|
||||
|
||||
|
||||
func clear(metric_id: StringName = &"") -> void:
|
||||
if metric_id.is_empty():
|
||||
_samples.clear()
|
||||
_ordered_samples.clear()
|
||||
_ordered_sample_bytes.clear()
|
||||
_integer_window_bytes.clear()
|
||||
_totals.clear()
|
||||
_sample_counts.clear()
|
||||
return
|
||||
_samples.erase(metric_id)
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
_integer_window_bytes.erase(metric_id)
|
||||
_totals.erase(metric_id)
|
||||
_sample_counts.erase(metric_id)
|
||||
|
||||
|
||||
func _aggregated_value(
|
||||
aggregation: int,
|
||||
average_us: int,
|
||||
p95_us: int,
|
||||
maximum_us: int
|
||||
) -> int:
|
||||
match aggregation:
|
||||
Aggregation.AVERAGE:
|
||||
return average_us
|
||||
Aggregation.MAXIMUM:
|
||||
return maximum_us
|
||||
_:
|
||||
return p95_us
|
||||
|
||||
|
||||
func _percentile(raw_values: Array, percentile: int) -> int:
|
||||
if raw_values.is_empty():
|
||||
return 0
|
||||
var values: Array[int] = []
|
||||
for raw_value: Variant in raw_values:
|
||||
values.append(int(raw_value))
|
||||
values.sort()
|
||||
var rank_numerator := clampi(percentile, 0, 100) * values.size()
|
||||
var index := maxi((rank_numerator + 99) / 100 - 1, 0)
|
||||
return values[mini(index, values.size() - 1)]
|
||||
|
||||
|
||||
static func _integer_window(values: Array) -> bool:
|
||||
for raw_value: Variant in values:
|
||||
if typeof(raw_value) != TYPE_INT: return false
|
||||
return true
|
||||
|
||||
|
||||
func _known_integer_window(metric_id: StringName, values: Array) -> bool:
|
||||
# Never retain unknown metrics. Exact bytes detect typed/untyped replacement,
|
||||
# same-size in-place edits and non-integer values before any cache reuse.
|
||||
if not _samples.has(metric_id):
|
||||
_integer_window_bytes.erase(metric_id)
|
||||
return _integer_window(values)
|
||||
var current_bytes := var_to_bytes(values)
|
||||
if _integer_window_bytes.has(metric_id) and _integer_window_bytes[metric_id] == current_bytes:
|
||||
return true
|
||||
if not _integer_window(values):
|
||||
_integer_window_bytes.erase(metric_id)
|
||||
return false
|
||||
_integer_window_bytes[metric_id] = current_bytes
|
||||
return true
|
||||
|
||||
|
||||
func _validated_sample_order(metric_id: StringName, values: Array) -> Array[int]:
|
||||
# No retained state for unknown metrics; raw dictionary removal also drops its index.
|
||||
if not _samples.has(metric_id):
|
||||
_ordered_samples.erase(metric_id)
|
||||
_ordered_sample_bytes.erase(metric_id)
|
||||
_integer_window_bytes.erase(metric_id)
|
||||
return []
|
||||
# Complete immutable raw bytes detect in-place/replaced windows, not just length.
|
||||
var current_bytes := var_to_bytes(values)
|
||||
if not _ordered_samples.has(metric_id) or _ordered_sample_bytes.get(metric_id, PackedByteArray()) != current_bytes:
|
||||
var ordered: Array[int] = []
|
||||
for raw_value: Variant in values: ordered.append(int(raw_value))
|
||||
ordered.sort()
|
||||
_ordered_samples[metric_id] = ordered
|
||||
_ordered_sample_bytes[metric_id] = current_bytes
|
||||
return _ordered_samples[metric_id]
|
||||
|
||||
|
||||
static func _legacy_report(owner: SimulationPerformanceBudget, metric_id: StringName) -> Dictionary:
|
||||
var values: Array = owner._samples.get(metric_id, [])
|
||||
var count := values.size()
|
||||
var lifetime_count := int(owner._sample_counts.get(metric_id, 0))
|
||||
var lifetime_total := int(owner._totals.get(metric_id, 0))
|
||||
var average_us := lifetime_total / lifetime_count if lifetime_count > 0 else 0
|
||||
var maximum_us := 0
|
||||
for raw_value: Variant in values:
|
||||
maximum_us = maxi(maximum_us, int(raw_value))
|
||||
var p95_us := owner._percentile(values, 95)
|
||||
var budget: Dictionary = owner._budgets.get(metric_id, {})
|
||||
var aggregation := int(budget.get(&"aggregation", Aggregation.P95))
|
||||
var measured_us := owner._aggregated_value(aggregation, average_us, p95_us, maximum_us)
|
||||
var limit_us := int(budget.get(&"limit_us", 0))
|
||||
return {
|
||||
&"metric_id": metric_id,
|
||||
&"window_sample_count": count,
|
||||
&"lifetime_sample_count": lifetime_count,
|
||||
&"average_us": average_us,
|
||||
&"p95_us": p95_us,
|
||||
&"maximum_us": maximum_us,
|
||||
&"aggregation": aggregation,
|
||||
&"measured_us": measured_us,
|
||||
&"limit_us": limit_us,
|
||||
&"budget_exceeded": limit_us > 0 and measured_us > limit_us,
|
||||
}
|
||||
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"review_id": "P23-02-native-integer-window-codec-build-preexecution-independent-review-r286",
|
||||
"reviewed_at": "2026-10-11T22:46:27.030725+00:00",
|
||||
"execution_allowed": true,
|
||||
"blockers": [],
|
||||
"evidence_sha256": {
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-r275.py": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-r275.py",
|
||||
"bytes": 16495,
|
||||
"sha256": "3f3c7118f9ad880a9f8ea0ed7f98940bd8a18a2edacf354fb9028ffba03f16de"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-preview-r275.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-preview-r275.json",
|
||||
"bytes": 3379,
|
||||
"sha256": "43d7ddf26df4b28e48699bae16de16442946890c3e302cd15e94a9e3cd84b8e8"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/input.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/input.json",
|
||||
"bytes": 900666,
|
||||
"sha256": "d2381f455e44d8355f54de0c52ed08d6ffbadbd68836848a87e96073c41e80e9"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/sdk/bin/libgodot-cpp.macos.template_debug.arm64.a": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/sdk/bin/libgodot-cpp.macos.template_debug.arm64.a",
|
||||
"bytes": 2251696,
|
||||
"sha256": "f429c29496622a7bfd460ebf249bb3930a515ca4fedd7efc6d347acab8015c35"
|
||||
},
|
||||
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++": {
|
||||
"path": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++",
|
||||
"bytes": 141373024,
|
||||
"sha256": "7def90dd8829726686213a747fc5bff1583df933dae5edc55d755479e0bfe00a"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/compile_commands.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76/compile_commands.json",
|
||||
"bytes": 46759,
|
||||
"sha256": "43cbba9c13c03bc8539efac1eaaf4da9b5d3d6a9af3875d8cc3a3994e1a59745"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-actual-independent-review-r133.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-actual-independent-review-r133.json",
|
||||
"bytes": 199445,
|
||||
"sha256": "c3cbeb45c1353dfea5f8181d10cc60f8a9b81db55d766ac5db902ac28c159a61"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-r133/input.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-r133/input.json",
|
||||
"bytes": 838584,
|
||||
"sha256": "bf6dafe0a55c9ffb0310ef5ede6471aac93f9dee4f4fe6ad84e20e998202514e"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-r133/libgodot-cpp.macos.template_debug.arm64.a": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-r133/libgodot-cpp.macos.template_debug.arm64.a",
|
||||
"bytes": 2284912,
|
||||
"sha256": "4d54f0980c3f445f3013cddb859135b619f28a91205fc8b9fbc9d10bc76c52c3"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76.py": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-bridge-build-r76.py",
|
||||
"bytes": 14637,
|
||||
"sha256": "e8cfbc4cea630e0cb245d825ac5daf9730e309c4895bcbc949a744f41a2d760a"
|
||||
},
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/SDKSettings.json": {
|
||||
"path": "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/SDKSettings.json",
|
||||
"bytes": 7774,
|
||||
"sha256": "f8d005f09381389167f9e0aeaa169bc9e7dff162ef22ca2fd8e98df7ff1acafe"
|
||||
},
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/SDKSettings.plist": {
|
||||
"path": "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/SDKSettings.plist",
|
||||
"bytes": 5388,
|
||||
"sha256": "e5c7c40b8c5dc1a9f99f8b9fa51870f8fe180421225b8201d0c4c826aad11bdc"
|
||||
},
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/cstddef": {
|
||||
"path": "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/c++/v1/cstddef",
|
||||
"bytes": 1607,
|
||||
"sha256": "53c4fc868f6439e48e52bbc9445a48eab1e597bbfad037ab7480216cb0c6832f"
|
||||
},
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libc++.tbd": {
|
||||
"path": "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib/libc++.tbd",
|
||||
"bytes": 155867,
|
||||
"sha256": "5539059b5e609a9518358e42234cc9a439ae0f54e5e8ca136686a8c9ebc35c1a"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-preexecution-independent-review-r275.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-preexecution-independent-review-r275.json",
|
||||
"bytes": 21406,
|
||||
"sha256": "38639f46a7dedb6926351ba5ded85cb0740ff4988e7ecff0360817696257b76a"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-actual-independent-review-r275.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-build-actual-independent-review-r275.json",
|
||||
"bytes": 21552,
|
||||
"sha256": "54576596f82b8aceca440c1a5514ce811125e977c64c210675feecbb3901929f"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286.py": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286.py",
|
||||
"bytes": 16592,
|
||||
"sha256": "14bdaa2dcfab62f594fcb95a9ead5bc6defbc35e727fbb0de46a7494af780e3a"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-preview-r286.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-preview-r286.json",
|
||||
"bytes": 3402,
|
||||
"sha256": "cba922a40684eaf3206c17ac8eaa588b04015a9f09d0dbc5f79a16bf7f4159b2"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285-r02.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285-r02.json",
|
||||
"bytes": 10950,
|
||||
"sha256": "bffbdcb491ba0aa68eeac764228c04418d2ef3834624cb1fcb98db773e8369fd"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-root-verification-r285-r02.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-root-verification-r285-r02.json",
|
||||
"bytes": 4849,
|
||||
"sha256": "27b0b957737d79a3047f2b2a8fa936ac0ec368414d61bb6f097a524a3fd27ead"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp",
|
||||
"bytes": 59396,
|
||||
"sha256": "75bc2f659f62889fccac5990461f5b51e8b87f30ca0791e4a4c542d37a7c5236"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py": {
|
||||
"path": "/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py",
|
||||
"bytes": 8607,
|
||||
"sha256": "7316b5b10bc65f7e286b66940767175f1f6b07182b84c8c2b326907bc668f9b5"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json",
|
||||
"bytes": 14529,
|
||||
"sha256": "40b90fe5086d1bb468f14418657b946fac11615b46cdf66de5f0ccac832a8457"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json",
|
||||
"bytes": 4325,
|
||||
"sha256": "38fb6adc0ec74c0f37631da3f1ddecf185879be9655574a920f1c12566beaa8f"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json",
|
||||
"bytes": 9547,
|
||||
"sha256": "c3f3525ee6b172bcbbc79838d49beac137d2dbd86deb61cdbefc1a921e743f06"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp",
|
||||
"bytes": 59281,
|
||||
"sha256": "2edd9d6d782cd4072272f0c24b50ce74691899c5a4bbc9ccc946a991217d814a"
|
||||
}
|
||||
},
|
||||
"canonical_doc4_context_pins": {
|
||||
"IMPLEMENTATION_PLAN.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 492762,
|
||||
"sha256": "c2f4824ac3f47231395820ef792d88884a6d7c578f9f6c7b1379619c25fe7a01"
|
||||
},
|
||||
"PROGRESS_INTEGRATION.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 410826,
|
||||
"sha256": "9eaf70befb9b94d0b890caef1d8afd8f6348a3b6621fd03d5fd469f9638806c0"
|
||||
},
|
||||
"work_packages.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json",
|
||||
"bytes": 1180547,
|
||||
"sha256": "f6538388b31fecba52276fe90dca327afc00a54ea530a8b4ecddfc87fd32ae7e"
|
||||
},
|
||||
"progress_snapshot.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"bytes": 529159,
|
||||
"sha256": "9a03b385c8e53570012a0e3b977a8c78a3cec11ea8d5f585841f5abff3c19517"
|
||||
}
|
||||
},
|
||||
"archival_before_pins": {
|
||||
"accepted275_context271": {
|
||||
"IMPLEMENTATION_PLAN.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-candidate-r276/before/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 511898,
|
||||
"sha256": "de1f65be5107f297b6737749b89f93c066beb39f223689a27be3698611fdb9bc"
|
||||
},
|
||||
"PROGRESS_INTEGRATION.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-candidate-r276/before/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 429962,
|
||||
"sha256": "b259f86fe8d9709cf8f943b90aabae4e2ea2457dfce82eaa51a7edf23160b66b"
|
||||
},
|
||||
"work_packages.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-candidate-r276/before/work_packages.json",
|
||||
"bytes": 1247101,
|
||||
"sha256": "d058f5d00a98bc162ffefa362c9dce2602adf7ce4cfac0317d5d9de580e96977"
|
||||
},
|
||||
"progress_snapshot.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-candidate-r276/before/progress_snapshot.json",
|
||||
"bytes": 524354,
|
||||
"sha256": "99e45a9ae230926987de481123e977a9b0d49fdcc21f1e450d74fc1e2467ccc5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"allowed_scope": {
|
||||
"bridge_compile": 1,
|
||||
"compile_row": 62,
|
||||
"link": 1,
|
||||
"SDK_compile": 0,
|
||||
"SDK133_archive_reuse": true,
|
||||
"old_bridge_object_reuse": false,
|
||||
"Godot_load": 0,
|
||||
"stage_clone": 0,
|
||||
"watchdog_seconds_each": 90
|
||||
},
|
||||
"findings": [
|
||||
"Read-only full diff against authenticated275 driver: candidate/review/root/preview/output labels; candidate_cpp/header lookup for input schema; pre-root reviewSHA binding; strict stdout-empty strengthening. Ownedcompiler/run_build_phase/final cleanup/source-default-doc/candidate/SDK protections unchanged.",
|
||||
"Preview full2argv inverse candidate/outputpaths equals old275 exactly. Compiler flags retain exceptions, Wall/Wextra/Werror, arm64 and authenticatedmacOSSDK; only bridge row062 plus link with unchanged133archive.",
|
||||
"Static285r02/root and current282 pins fresh; source/header/primaryencoder/original285reject authenticated. Accepted275 oldcurrent271 maps C276/before, not live282.",
|
||||
"Capacity528MiB admission satisfied at read time, floor512 and growth16 maintained with runtimechecks. Planned2–30seconds is estimate only. Source/default freshly match latest authenticated preintegration evidence.",
|
||||
"Buildpreflight authorizes only prepared1compile/1link; no ABI/load/runtimecodec/localwhole/performance/adoption/unit acceptance. No fabricated preparation-author pin: provideddriver/preview are the reviewed artifacts."
|
||||
],
|
||||
"capacity_observation": {
|
||||
"free_bytes": 1318674432,
|
||||
"floor_bytes": 536870912,
|
||||
"growth_limit_bytes": 16777216,
|
||||
"admission_bytes": 553648128,
|
||||
"runtime_recheck_required": true
|
||||
},
|
||||
"remaining_unaccepted": [
|
||||
"Actualhostbuild and ABI/load",
|
||||
"Enginecodec typed/untyped32/64 empty/bounds/append-pop/externalalias/readonly/report/signal finite local",
|
||||
"S6/whole647/currentcandidate stableperformance/50ms/P95D10/final172/OSWindows/adoption/unit"
|
||||
],
|
||||
"reviewer_execution": {
|
||||
"compiler": 0,
|
||||
"link": 0,
|
||||
"engine": 0,
|
||||
"tests": 0,
|
||||
"validator": 0,
|
||||
"author": 0,
|
||||
"SDK": 0,
|
||||
"new_agents": 0,
|
||||
"existing_edits": 0
|
||||
},
|
||||
"pin_counts": {
|
||||
"direct": 32,
|
||||
"current": 4,
|
||||
"archive": 4
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
{
|
||||
"review_id": "P23-02-native-integer-window-codec-static-independent-review-r285-r02",
|
||||
"reviewed_at": "2026-10-11T22:44:32.400565+00:00",
|
||||
"implementation_supported": true,
|
||||
"static_accepted": true,
|
||||
"build_allowed": false,
|
||||
"execution_allowed": false,
|
||||
"blockers": [],
|
||||
"evidence_sha256": {
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp",
|
||||
"bytes": 59396,
|
||||
"sha256": "75bc2f659f62889fccac5990461f5b51e8b87f30ca0791e4a4c542d37a7c5236"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py": {
|
||||
"path": "/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py",
|
||||
"bytes": 8607,
|
||||
"sha256": "7316b5b10bc65f7e286b66940767175f1f6b07182b84c8c2b326907bc668f9b5"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json",
|
||||
"bytes": 14529,
|
||||
"sha256": "40b90fe5086d1bb468f14418657b946fac11615b46cdf66de5f0ccac832a8457"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json",
|
||||
"bytes": 4325,
|
||||
"sha256": "38fb6adc0ec74c0f37631da3f1ddecf185879be9655574a920f1c12566beaa8f"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json",
|
||||
"bytes": 9547,
|
||||
"sha256": "c3f3525ee6b172bcbbc79838d49beac137d2dbd86deb61cdbefc1a921e743f06"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp",
|
||||
"bytes": 59281,
|
||||
"sha256": "2edd9d6d782cd4072272f0c24b50ce74691899c5a4bbc9ccc946a991217d814a"
|
||||
}
|
||||
},
|
||||
"canonical_doc4_context_pins": {
|
||||
"IMPLEMENTATION_PLAN.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 492762,
|
||||
"sha256": "c2f4824ac3f47231395820ef792d88884a6d7c578f9f6c7b1379619c25fe7a01"
|
||||
},
|
||||
"PROGRESS_INTEGRATION.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 410826,
|
||||
"sha256": "9eaf70befb9b94d0b890caef1d8afd8f6348a3b6621fd03d5fd469f9638806c0"
|
||||
},
|
||||
"work_packages.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json",
|
||||
"bytes": 1180547,
|
||||
"sha256": "f6538388b31fecba52276fe90dca327afc00a54ea530a8b4ecddfc87fd32ae7e"
|
||||
},
|
||||
"progress_snapshot.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"bytes": 529159,
|
||||
"sha256": "9a03b385c8e53570012a0e3b977a8c78a3cec11ea8d5f585841f5abff3c19517"
|
||||
}
|
||||
},
|
||||
"repair_findings": [
|
||||
"Only original B1 repair: new temporary vector reserve/push_back removed. First pass validates each INT and computes bounded signed-int byte length; checked engine PackedByteArray.resize precedes second direct write pass. No new throwing temporary vector allocation in codec path.",
|
||||
"Repair inverse reconstructs exact frozen285 CPP; prior frozen285 complete inverse274 and unchanged primary source/header/Budget authentication remain applicable. All new direct/current282 pins freshly matched.",
|
||||
"Second pass reads same caller-owned Array in synchronous integer-only domain; index/type reads and integer conversion have no callback, and new output array is independent. No cross-call sample cache introduced. Arbitrary concurrent mutation is not supported.",
|
||||
"Per-object4 empty/boundary typed/untyped format comparisons, little endian typed/untyped32/64 layouts, append/pop update, external fullbytes mutation detection, oldencoder fallback and raw/order aliases unchanged. Existing parent report/Time/signal/GD ownership flow unchanged.",
|
||||
"This support concerns removal of the new allocation hazard, not a blanket OOM guarantee for all unchanged preexisting native/Godot allocations."
|
||||
],
|
||||
"prior_supported_static_findings": [
|
||||
"All declared direct and current282 doc4 pins freshly matched; CPP complete mechanical inverse equals exact parent274, header byteexact and BudgetGD unchanged. No stage generated.",
|
||||
"Local actual primary marshalls.cpp ed1daf0bf SHA183c43… confirms little-endian INT type header plus flag1<<16 outside signed32 range, unsigned two-complement payload4/8, Array header8 untyped or12 builtin typedINT, typed marker1<<16 and length32. No web rendering offsets used.",
|
||||
"Nine integer boundaries and empty typed/untyped arrays calibrate only per-object format boolean; mismatch preserves old UtilityFunctions encoder. No cached sample values or global objects added.",
|
||||
"For locally produced exact bytes, update keeps typed prefix and order, changes length and appends exact duration; popFront removes one original element, including zero/negative capacity and empty append-then-pop. Signed total/count/P95/report calculations unchanged from parent.",
|
||||
"Complete current raw bytes still computed before existing fullbyte cache comparison, so external same-size/value/type mutation is not hidden. Original raw/reusedordered aliases and readonly/custom/malformed guards retained; after-allocation failure path invokes original encoder. No signal/Time/helper/publicreport sequencing changes in CPP inverse; GD original signaldeepdup/return unaffected."
|
||||
],
|
||||
"required_next_gates": [
|
||||
"Separate authenticated bridge compile/link preexecution review; SDK methods/header/format runtime not compile-tested by static review.",
|
||||
"Load and exact engine-codec helper comparisons: empty/typed/untyped, 32/64 allsigned bounds, append/pop0/-1/1/window600, mixed/read-only/custom/rawexternedit/alias/report/signal/reentry finite scopes.",
|
||||
"Same candidate original S6 impact and whole647 full-save equality, finite exploratory timing only. No stable gain/performancecause/P95/D10/50ms/final172/OS/Windows/adoption/unit acceptance."
|
||||
],
|
||||
"inference_domain": "Exact synchronous integer Array values and fixed authenticated engine encoder layout; calibration mismatch uses original encoder. No arbitrary concurrency or allocator-failure runtime proof.",
|
||||
"candidate_preserved": true,
|
||||
"performance_cause_or_gain_claimed": false,
|
||||
"reviewer_execution": {
|
||||
"engine": 0,
|
||||
"compiler": 0,
|
||||
"tests": 0,
|
||||
"validator": 0,
|
||||
"SDK": 0,
|
||||
"author": 0,
|
||||
"codec": 0,
|
||||
"new_agents": 0,
|
||||
"existing_file_edits": 0
|
||||
},
|
||||
"pin_counts": {
|
||||
"direct": 12,
|
||||
"current": 4
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"passed": true,
|
||||
"review_sha256": "bffbdcb491ba0aa68eeac764228c04418d2ef3834624cb1fcb98db773e8369fd",
|
||||
"verified_entries": [
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp",
|
||||
"bytes": 59396,
|
||||
"sha256": "75bc2f659f62889fccac5990461f5b51e8b87f30ca0791e4a4c542d37a7c5236"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
{
|
||||
"path": "/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py",
|
||||
"bytes": 8607,
|
||||
"sha256": "7316b5b10bc65f7e286b66940767175f1f6b07182b84c8c2b326907bc668f9b5"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json",
|
||||
"bytes": 14529,
|
||||
"sha256": "40b90fe5086d1bb468f14418657b946fac11615b46cdf66de5f0ccac832a8457"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json",
|
||||
"bytes": 4325,
|
||||
"sha256": "38fb6adc0ec74c0f37631da3f1ddecf185879be9655574a920f1c12566beaa8f"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json",
|
||||
"bytes": 9547,
|
||||
"sha256": "c3f3525ee6b172bcbbc79838d49beac137d2dbd86deb61cdbefc1a921e743f06"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp",
|
||||
"bytes": 59281,
|
||||
"sha256": "2edd9d6d782cd4072272f0c24b50ce74691899c5a4bbc9ccc946a991217d814a"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 492762,
|
||||
"sha256": "c2f4824ac3f47231395820ef792d88884a6d7c578f9f6c7b1379619c25fe7a01"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 410826,
|
||||
"sha256": "9eaf70befb9b94d0b890caef1d8afd8f6348a3b6621fd03d5fd469f9638806c0"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json",
|
||||
"bytes": 1180547,
|
||||
"sha256": "f6538388b31fecba52276fe90dca327afc00a54ea530a8b4ecddfc87fd32ae7e"
|
||||
},
|
||||
{
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"bytes": 529159,
|
||||
"sha256": "9a03b385c8e53570012a0e3b977a8c78a3cec11ea8d5f585841f5abff3c19517"
|
||||
}
|
||||
],
|
||||
"unit_accepted": false
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
{
|
||||
"review_id": "P23-02-native-integer-window-codec-static-independent-review-r285",
|
||||
"reviewed_at": "2026-10-11T22:43:51.086271+00:00",
|
||||
"implementation_supported": false,
|
||||
"static_accepted": false,
|
||||
"build_allowed": false,
|
||||
"execution_allowed": false,
|
||||
"blockers": [
|
||||
{
|
||||
"id": "B1-unhandled-temporary-allocation",
|
||||
"function": "integer_wire_encode",
|
||||
"detail": "std::vector<int64_t> integers.reserve(count) and push_back introduce a new unchecked C++ allocation, before complete element-type validation. PackedByteArray.resize error handling does not cover this allocation. bad_alloc/length_error escapes if exceptions are enabled; an exception-disabled configuration can terminate. Therefore the stated failed-allocation false/fallback contract is not established, including large unsupported arrays that originally return on first noninteger element.",
|
||||
"required_repair": "Remove the temporary vector with two read-only passes: first validate types/compute bounded length, then after checked PackedByteArray.resize write each unchanged integer directly. Alternatively provide an authenticated allocation-failure path compatible with actual build exception policy; do not merely weaken accepted domain or ignore failure."
|
||||
}
|
||||
],
|
||||
"evidence_sha256": {
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp",
|
||||
"bytes": 59281,
|
||||
"sha256": "2edd9d6d782cd4072272f0c24b50ce74691899c5a4bbc9ccc946a991217d814a"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_core.hpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"/private/tmp/thetree-prepare-integer-window-codec-r285.py": {
|
||||
"path": "/private/tmp/thetree-prepare-integer-window-codec-r285.py",
|
||||
"bytes": 8488,
|
||||
"sha256": "ef011afc1f7a8d590330ab9f24bbd973b5bdda6e1923675f55c4f4c83ed9d93b"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json",
|
||||
"bytes": 14529,
|
||||
"sha256": "40b90fe5086d1bb468f14418657b946fac11615b46cdf66de5f0ccac832a8457"
|
||||
},
|
||||
"/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/input.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/input.json",
|
||||
"bytes": 4313,
|
||||
"sha256": "58dd63b112841a6908e8356741a4f7ca949d29f917c50ecba24be5859b1d1a6c"
|
||||
}
|
||||
},
|
||||
"canonical_doc4_context_pins": {
|
||||
"IMPLEMENTATION_PLAN.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 492762,
|
||||
"sha256": "c2f4824ac3f47231395820ef792d88884a6d7c578f9f6c7b1379619c25fe7a01"
|
||||
},
|
||||
"PROGRESS_INTEGRATION.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 410826,
|
||||
"sha256": "9eaf70befb9b94d0b890caef1d8afd8f6348a3b6621fd03d5fd469f9638806c0"
|
||||
},
|
||||
"work_packages.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json",
|
||||
"bytes": 1180547,
|
||||
"sha256": "f6538388b31fecba52276fe90dca327afc00a54ea530a8b4ecddfc87fd32ae7e"
|
||||
},
|
||||
"progress_snapshot.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"bytes": 529159,
|
||||
"sha256": "9a03b385c8e53570012a0e3b977a8c78a3cec11ea8d5f585841f5abff3c19517"
|
||||
}
|
||||
},
|
||||
"supported_static_findings": [
|
||||
"All declared direct and current282 doc4 pins freshly matched; CPP complete mechanical inverse equals exact parent274, header byteexact and BudgetGD unchanged. No stage generated.",
|
||||
"Local actual primary marshalls.cpp ed1daf0bf SHA183c43… confirms little-endian INT type header plus flag1<<16 outside signed32 range, unsigned two-complement payload4/8, Array header8 untyped or12 builtin typedINT, typed marker1<<16 and length32. No web rendering offsets used.",
|
||||
"Nine integer boundaries and empty typed/untyped arrays calibrate only per-object format boolean; mismatch preserves old UtilityFunctions encoder. No cached sample values or global objects added.",
|
||||
"For locally produced exact bytes, update keeps typed prefix and order, changes length and appends exact duration; popFront removes one original element, including zero/negative capacity and empty append-then-pop. Signed total/count/P95/report calculations unchanged from parent.",
|
||||
"Complete current raw bytes still computed before existing fullbyte cache comparison, so external same-size/value/type mutation is not hidden. Original raw/reusedordered aliases and readonly/custom/malformed guards retained; after-allocation failure path invokes original encoder. No signal/Time/helper/publicreport sequencing changes in CPP inverse; GD original signaldeepdup/return unaffected."
|
||||
],
|
||||
"remaining_required": [
|
||||
"Static allocation repair before build preflight; compile/link new methods/API not yet verified.",
|
||||
"Local original engine var_to_bytes comparison across typed/untyped empty and signed32/64 edges, append/pop/capacity0/-1/1/window600, raw external edits, readonly/mixed/custom/alias and original report/signal/reentry ownership scopes.",
|
||||
"Same current candidate original S6 impact and whole647 full-save equality plus finite observation before any performance conclusion.",
|
||||
"General arbitrary ownership/invalidinput/reentry/OOM-runtime behavior, stable gain/P95/D10/50ms/final172/OS/Windows/adoption/unit acceptance remain unproved."
|
||||
],
|
||||
"reviewer_execution": {
|
||||
"engine": 0,
|
||||
"compiler": 0,
|
||||
"tests": 0,
|
||||
"validator": 0,
|
||||
"SDK": 0,
|
||||
"author": 0,
|
||||
"codec": 0,
|
||||
"new_agents": 0,
|
||||
"existing_file_edits": 0
|
||||
},
|
||||
"pin_counts": {
|
||||
"direct": 10,
|
||||
"current": 4
|
||||
},
|
||||
"candidate_preserved": true,
|
||||
"performance_cause_or_gain_claimed": false
|
||||
}
|
||||
+923
@@ -0,0 +1,923 @@
|
||||
// Isolated, uncompiled bridge prototype. No production registration or integration yet.
|
||||
#include "tree_core.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <godot_cpp/classes/ref_counted.hpp>
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/variant/vector3i.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
#include <godot_cpp/variant/array.hpp>
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
#include <godot_cpp/variant/packed_int64_array.hpp>
|
||||
#include <godot_cpp/variant/typed_array.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
using namespace godot;
|
||||
using the_tree_native::I;
|
||||
using the_tree_native::Edge;
|
||||
|
||||
class TheTreeNumericCore : public RefCounted {
|
||||
GDCLASS(TheTreeNumericCore, RefCounted)
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("performance_record_sample", "owner", "metric_id", "duration_us"), &TheTreeNumericCore::performance_record_sample);
|
||||
ClassDB::bind_method(D_METHOD("encode_integer_window", "values"), &TheTreeNumericCore::encode_integer_window);
|
||||
ClassDB::bind_method(D_METHOD("guest_tree_edge_rows", "path", "current", "layers", "access"), &TheTreeNumericCore::guest_tree_edge_rows);
|
||||
ClassDB::bind_method(D_METHOD("search", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search);
|
||||
ClassDB::bind_method(D_METHOD("search_cached", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search_cached);
|
||||
ClassDB::bind_method(D_METHOD("clear_edge_cache"), &TheTreeNumericCore::clear_edge_cache);
|
||||
ClassDB::bind_method(D_METHOD("nearest_guest_access", "path", "start", "goals", "fallback_target", "layers", "access", "ticks"), &TheTreeNumericCore::nearest_guest_access);
|
||||
ClassDB::bind_method(D_METHOD("project_guest_candidates", "runtime", "guest_id", "open_state", "degraded_state", "movement_scale", "stair_credit", "orthogonal_cost", "ticks"), &TheTreeNumericCore::project_guest_candidates);
|
||||
}
|
||||
private:
|
||||
// Cache belongs to this instance and one caller-owned graph workspace.
|
||||
// No reuse after cells, indices, filters or edge values change: the caller
|
||||
// must clear it or release this core. The original search never uses it.
|
||||
std::map<I, std::vector<Edge>> cached_edges;
|
||||
// Godot ed1daf0bf's integer Array wire layout; checked against this engine
|
||||
// once per core, never sample values. Every caller-owned Array stays intact.
|
||||
bool integer_wire_checked=false;
|
||||
bool integer_wire_compatible=false;
|
||||
static void wire32(uint8_t *p,uint32_t value) {
|
||||
for (unsigned i=0;i<4;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static void wire64(uint8_t *p,uint64_t value) {
|
||||
for (unsigned i=0;i<8;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static int64_t integer_wire_width(int64_t value) {
|
||||
return value<int64_t(INT32_MIN) || value>int64_t(INT32_MAX) ? 12 : 8;
|
||||
}
|
||||
static void wire_integer(uint8_t *p,int64_t value) {
|
||||
const bool wide=integer_wire_width(value)==12;
|
||||
wire32(p,uint32_t(Variant::INT) | (wide ? uint32_t(1)<<16 : 0));
|
||||
if (wide) wire64(p+4,uint64_t(value)); else wire32(p+4,uint32_t(value));
|
||||
}
|
||||
static bool integer_wire_encode(const Array &values,PackedByteArray &out) {
|
||||
const bool typed=values.is_typed();
|
||||
if (typed && (values.get_typed_builtin()!=Variant::INT ||
|
||||
!values.get_typed_class_name().is_empty() || values.get_typed_script().get_type()!=Variant::NIL)) return false;
|
||||
const int64_t count=values.size(),header=typed ? 12 : 8;
|
||||
// The engine encoder uses a signed-int byte length. Preserve fallback
|
||||
// for oversized inputs and failed allocations, before owner mutation.
|
||||
if (count<0 || count>(int64_t(INT32_MAX)-header)/12) return false;
|
||||
std::vector<int64_t> integers;
|
||||
integers.reserve(size_t(count));
|
||||
int64_t size=header;
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const Variant value=values[i];
|
||||
if (value.get_type()!=Variant::INT) return false;
|
||||
const int64_t integer=value;
|
||||
integers.push_back(integer);size+=integer_wire_width(integer);
|
||||
}
|
||||
if (out.resize(size)!=OK) return false;
|
||||
uint8_t *p=out.ptrw();
|
||||
wire32(p,uint32_t(Variant::ARRAY) | (typed ? uint32_t(1)<<16 : 0));
|
||||
int64_t offset=4;
|
||||
if (typed) {wire32(p+offset,uint32_t(Variant::INT));offset+=4;}
|
||||
wire32(p+offset,uint32_t(count));offset+=4;
|
||||
for (const int64_t value: integers) {wire_integer(p+offset,value);offset+=integer_wire_width(value);}
|
||||
return offset==size;
|
||||
}
|
||||
bool integer_wire_ready() {
|
||||
if (integer_wire_checked) return integer_wire_compatible;
|
||||
Array empty,probe;
|
||||
TypedArray<int64_t> typed_empty,typed_probe;
|
||||
const int64_t boundaries[]={INT64_MIN,int64_t(INT32_MIN)-1,INT32_MIN,-1,0,1,INT32_MAX,int64_t(INT32_MAX)+1,INT64_MAX};
|
||||
for (const int64_t value: boundaries) {probe.append(value);typed_probe.append(value);}
|
||||
bool matches=true;
|
||||
for (const Array &value: {empty,probe,Array(typed_empty),Array(typed_probe)}) {
|
||||
PackedByteArray encoded;
|
||||
matches=integer_wire_encode(value,encoded) && encoded==UtilityFunctions::var_to_bytes(value) && matches;
|
||||
}
|
||||
integer_wire_compatible=matches;integer_wire_checked=true;
|
||||
return matches;
|
||||
}
|
||||
static bool integer_wire_update(const PackedByteArray &before,bool typed,int64_t old_count,
|
||||
int64_t appended,bool pop_front,PackedByteArray &after) {
|
||||
const int64_t header=typed ? 12 : 8;
|
||||
if (before.size()<header || old_count<0) return false;
|
||||
const bool remove_appended=pop_front && old_count==0;
|
||||
int64_t skipped=0;
|
||||
if (pop_front && old_count>0) {
|
||||
if (before.size()<header+8) return false;
|
||||
// Exact INT header was produced locally from the current values.
|
||||
skipped=(before[header+2] & 1) ? 12 : 8;
|
||||
if (before.size()<header+skipped) return false;
|
||||
}
|
||||
const int64_t added=remove_appended ? 0 : integer_wire_width(appended);
|
||||
const int64_t size=before.size()-skipped+added;
|
||||
if (size>INT32_MAX || after.resize(size)!=OK) return false;
|
||||
uint8_t *p=after.ptrw();const uint8_t *q=before.ptr();
|
||||
std::memcpy(p,q,size_t(header));
|
||||
wire32(p+header-4,uint32_t(old_count+1-(pop_front ? 1 : 0)));
|
||||
const int64_t kept=before.size()-header-skipped;
|
||||
if (kept>0) std::memcpy(p+header,q+header+skipped,size_t(kept));
|
||||
if (added>0) wire_integer(p+header+kept,appended);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Per-public-call immutable names. No static/global lifetime, cached
|
||||
// object values, script dispatch result or profile; recursive calls own
|
||||
// separate scopes. Destruction precedes leaving the engine call.
|
||||
struct ProjectionNames {
|
||||
const StringName k_ACCESS_LAYER_DENIED{"ACCESS_LAYER_DENIED",false};
|
||||
const StringName k_CONNECTOR{"CONNECTOR",false};
|
||||
const StringName k_GOAL_NOT_PASSABLE{"GOAL_NOT_PASSABLE",false};
|
||||
const StringName k_GOAL_TEMPORARILY_BLOCKED{"GOAL_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k_GOAL_UNREACHABLE{"GOAL_UNREACHABLE",false};
|
||||
const StringName k_OK{"OK",false};
|
||||
const StringName k_START_NOT_PASSABLE{"START_NOT_PASSABLE",false};
|
||||
const StringName k_START_TEMPORARILY_BLOCKED{"START_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k__build_guest_query_tree_compact{"_build_guest_query_tree_compact",false};
|
||||
const StringName k__cache_key{"_cache_key",false};
|
||||
const StringName k__cell_traffic{"_cell_traffic",false};
|
||||
const StringName k__copy_path{"_copy_path",false};
|
||||
const StringName k__diagnose_no_route_reason{"_diagnose_no_route_reason",false};
|
||||
const StringName k__find_facility_access_path{"_find_facility_access_path",false};
|
||||
const StringName k__find_path_internal{"_find_path_internal",false};
|
||||
const StringName k__guest_access_batch_can_select{"_guest_access_batch_can_select",false};
|
||||
const StringName k__guest_batch_has_permitted_layer_crossing{"_guest_batch_has_permitted_layer_crossing",false};
|
||||
const StringName k__guest_has_checked_in{"_guest_has_checked_in",false};
|
||||
const StringName k__guest_step_connector_memo{"_guest_step_connector_memo",false};
|
||||
const StringName k__guest_tree_arithmetic_wrapped{"_guest_tree_arithmetic_wrapped",false};
|
||||
const StringName k__guest_tree_reconstruct_path{"_guest_tree_reconstruct_path",false};
|
||||
const StringName k__need_relief_for_facility_tags{"_need_relief_for_facility_tags",false};
|
||||
const StringName k__passable_cells{"_passable_cells",false};
|
||||
const StringName k__path_cache{"_path_cache",false};
|
||||
const StringName k__query_batch{"_query_batch",false};
|
||||
const StringName k__route_failure{"_route_failure",false};
|
||||
const StringName k__service_category_for_tags{"_service_category_for_tags",false};
|
||||
const StringName k__sorted_name_keys{"_sorted_name_keys",false};
|
||||
const StringName k_arithmetic_wrapped{"arithmetic_wrapped",false};
|
||||
const StringName k_available_quantity{"available_quantity",false};
|
||||
const StringName k_base_quality{"base_quality",false};
|
||||
const StringName k_base_service_minutes{"base_service_minutes",false};
|
||||
const StringName k_begin_guest_path_query_batch{"begin_guest_path_query_batch",false};
|
||||
const StringName k_cache_misses{"cache_misses",false};
|
||||
const StringName k_calculate_guest_batch_path_cost{"calculate_guest_batch_path_cost",false};
|
||||
const StringName k_candidates{"candidates",false};
|
||||
const StringName k_capacity{"capacity",false};
|
||||
const StringName k_compact{"compact",false};
|
||||
const StringName k_cost{"cost",false};
|
||||
const StringName k_cost_values{"cost_values",false};
|
||||
const StringName k_costs{"costs",false};
|
||||
const StringName k_current_cell{"current_cell",false};
|
||||
const StringName k_definition_id{"definition_id",false};
|
||||
const StringName k_definition_registry{"definition_registry",false};
|
||||
const StringName k_elevators{"elevators",false};
|
||||
const StringName k_end_guest_path_query_batch{"end_guest_path_query_batch",false};
|
||||
const StringName k_entrance{"entrance",false};
|
||||
const StringName k_estimated_total_gold{"estimated_total_gold",false};
|
||||
const StringName k_expected_quality{"expected_quality",false};
|
||||
const StringName k_expected_wait{"expected_wait",false};
|
||||
const StringName k_facility_id{"facility_id",false};
|
||||
const StringName k_facility_service{"facility_service",false};
|
||||
const StringName k_facility_tags{"facility_tags",false};
|
||||
const StringName k_get_connector{"get_connector",false};
|
||||
const StringName k_get_facility{"get_facility",false};
|
||||
const StringName k_get_facility_business_price{"get_facility_business_price",false};
|
||||
const StringName k_get_guest{"get_guest",false};
|
||||
const StringName k_get_instances{"get_instances",false};
|
||||
const StringName k_get_step_connector{"get_step_connector",false};
|
||||
const StringName k_graph_revision{"graph_revision",false};
|
||||
const StringName k_group_fit{"group_fit",false};
|
||||
const StringName k_guest_service{"guest_service",false};
|
||||
const StringName k_illegal_level{"illegal_level",false};
|
||||
const StringName k_indices{"indices",false};
|
||||
const StringName k_inputs_available{"inputs_available",false};
|
||||
const StringName k_instance_id{"instance_id",false};
|
||||
const StringName k_inventory_inputs{"inventory_inputs",false};
|
||||
const StringName k_inventory_service{"inventory_service",false};
|
||||
const StringName k_is_open{"is_open",false};
|
||||
const StringName k_last_error{"last_error",false};
|
||||
const StringName k_last_no_route_reason{"last_no_route_reason",false};
|
||||
const StringName k_last_path_cost{"last_path_cost",false};
|
||||
const StringName k_local_inventory_id{"local_inventory_id",false};
|
||||
const StringName k_movement_type{"movement_type",false};
|
||||
const StringName k_need_relief{"need_relief",false};
|
||||
const StringName k_occupied_cells{"occupied_cells",false};
|
||||
const StringName k_origin{"origin",false};
|
||||
const StringName k_owns_connector{"owns_connector",false};
|
||||
const StringName k_path{"path",false};
|
||||
const StringName k_path_handle{"path_handle",false};
|
||||
const StringName k_path_query_measured{"path_query_measured",false};
|
||||
const StringName k_path_service{"path_service",false};
|
||||
const StringName k_queue_entries{"queue_entries",false};
|
||||
const StringName k_reachable{"reachable",false};
|
||||
const StringName k_ready{"ready",false};
|
||||
const StringName k_resource_path{"resource_path",false};
|
||||
const StringName k_risk_level{"risk_level",false};
|
||||
const StringName k_service_category{"service_category",false};
|
||||
const StringName k_settled{"settled",false};
|
||||
const StringName k_staff_only{"staff_only",false};
|
||||
const StringName k_start{"start",false};
|
||||
const StringName k_state{"state",false};
|
||||
const StringName k_supported{"supported",false};
|
||||
const StringName k_tags{"tags",false};
|
||||
const StringName k_tagproj_hunger{"hunger",false};
|
||||
const StringName k_tagproj_sleep{"sleep",false};
|
||||
const StringName k_tagproj_hygiene{"hygiene",false};
|
||||
const StringName k_tagproj_medical{"medical",false};
|
||||
const StringName k_tagproj_leisure{"leisure",false};
|
||||
const StringName k_tagproj_intimacy{"intimacy",false};
|
||||
const StringName k_tagproj_dining{"dining",false};
|
||||
const StringName k_tagproj_lodging{"lodging",false};
|
||||
const StringName k_tagproj_social{"social",false};
|
||||
const StringName k_tagproj_bath{"bath",false};
|
||||
const StringName k_tagproj_wellness{"wellness",false};
|
||||
const StringName k_tagproj_adult_only{"adult_only",false};
|
||||
const StringName k_tagproj_restricted_service{"restricted_service",false};
|
||||
const StringName k_tagproj_food{"food",false};
|
||||
const StringName k_tagproj_service{"service",false};
|
||||
const StringName k_target_cell{"target_cell",false};
|
||||
const StringName k_targets{"targets",false};
|
||||
const StringName k_temporary_block{"temporary_block",false};
|
||||
const StringName k_travel_minutes{"travel_minutes",false};
|
||||
const StringName k_tree{"tree",false};
|
||||
const StringName k_usable{"usable",false};
|
||||
};
|
||||
public:
|
||||
Dictionary encode_integer_window(const Array &values) {
|
||||
Dictionary result;const StringName supported("supported",false),bytes("bytes",false);
|
||||
result[supported]=false;PackedByteArray encoded;
|
||||
if (!integer_wire_ready() || !integer_wire_encode(values,encoded)) return result;
|
||||
result[supported]=true;result[bytes]=encoded;return result;
|
||||
}
|
||||
// Whole builtin integer record operation. No retained sample values or time
|
||||
// suppression. Unsupported data returns before any caller-owned mutation.
|
||||
Dictionary performance_record_sample(Object *owner, const StringName &metric, int64_t duration) {
|
||||
const StringName supported("supported",false), report_key("report",false);
|
||||
Dictionary unsupported; unsupported[supported]=false;
|
||||
Object *script=owner ? owner->get_script() : nullptr;
|
||||
if (!script || String(script->get(StringName("resource_path",false))) != "res://scripts/core/performance_budget.gd" || metric==StringName() || duration<0) return unsupported;
|
||||
const StringName samples_key("_samples",false), ordered_key("_ordered_samples",false), bytes_key("_ordered_sample_bytes",false), integer_key("_integer_window_bytes",false), totals_key("_totals",false), counts_key("_sample_counts",false), budgets_key("_budgets",false);
|
||||
const Variant sv=owner->get(samples_key), ov=owner->get(ordered_key), bv=owner->get(bytes_key), iv=owner->get(integer_key), tv=owner->get(totals_key), cv=owner->get(counts_key), uv=owner->get(budgets_key), capacityv=owner->get(StringName("sample_capacity",false));
|
||||
if (sv.get_type()!=Variant::DICTIONARY || ov.get_type()!=Variant::DICTIONARY || bv.get_type()!=Variant::DICTIONARY || iv.get_type()!=Variant::DICTIONARY || tv.get_type()!=Variant::DICTIONARY || cv.get_type()!=Variant::DICTIONARY || uv.get_type()!=Variant::DICTIONARY || capacityv.get_type()!=Variant::INT) return unsupported;
|
||||
Dictionary samples=sv, orders=ov, rawbytes=bv, integers=iv, totals=tv, counts=cv, budgets=uv;
|
||||
if (samples.is_read_only() || orders.is_read_only() || rawbytes.is_read_only() || integers.is_read_only() || totals.is_read_only() || counts.is_read_only()) return unsupported;
|
||||
const std::vector<Dictionary> maps{samples,orders,rawbytes,integers,totals,counts,budgets};
|
||||
for (size_t i=0;i<maps.size();++i) for (size_t j=i+1;j<maps.size();++j)
|
||||
if (UtilityFunctions::is_same(maps[i],maps[j])) return unsupported;
|
||||
const bool existing=samples.has(metric);
|
||||
Variant rawv=samples.get(metric,Array());
|
||||
if (rawv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array values=rawv;
|
||||
if (values.is_read_only()) return unsupported;
|
||||
|
||||
Variant totalv=totals.get(metric,0), countv=counts.get(metric,0), budgetv=budgets.get(metric,Dictionary());
|
||||
if (totalv.get_type()!=Variant::INT || countv.get_type()!=Variant::INT || budgetv.get_type()!=Variant::DICTIONARY) return unsupported;
|
||||
Dictionary budget=budgetv;
|
||||
for (const Dictionary &map: maps) if (UtilityFunctions::is_same(budget,map)) return unsupported;
|
||||
const StringName aggregation_key("aggregation",false), limit_key("limit_us",false);
|
||||
Variant aggregationv=budget.get(aggregation_key,1), limitv=budget.get(limit_key,0);
|
||||
if (aggregationv.get_type()!=Variant::INT || limitv.get_type()!=Variant::INT) return unsupported;
|
||||
// One exact byte snapshot serves both builtin validation helpers inside
|
||||
// this call; there are no external calls between their original checks.
|
||||
PackedByteArray before;
|
||||
const bool fast_wire=integer_wire_ready() && integer_wire_encode(values,before);
|
||||
if (!fast_wire) {
|
||||
for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;
|
||||
before=UtilityFunctions::var_to_bytes(values);
|
||||
}
|
||||
const int64_t old_count=values.size();
|
||||
TypedArray<int64_t> ordered;
|
||||
Variant savedbytes=rawbytes.get(metric,PackedByteArray());
|
||||
bool reuse=existing && orders.has(metric) && savedbytes.get_type()==Variant::PACKED_BYTE_ARRAY && PackedByteArray(savedbytes)==before;
|
||||
if (reuse) {
|
||||
Variant orderv=orders[metric];
|
||||
if (orderv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array prior=orderv;
|
||||
if (prior.is_read_only() || !prior.is_typed() || prior.get_typed_builtin()!=Variant::INT || prior.size()!=values.size()) return unsupported;
|
||||
ordered=prior;
|
||||
} else {
|
||||
for (int64_t i=0;i<values.size();++i) ordered.append(values[i]);
|
||||
ordered.sort();
|
||||
}
|
||||
// Guards completed: mutations preserve raw and reused ordered aliases.
|
||||
if (!existing) samples[metric]=values;
|
||||
integers[metric]=before;
|
||||
if (!reuse) { orders[metric]=ordered; rawbytes[metric]=before; }
|
||||
values.append(duration);
|
||||
ordered.insert(ordered.bsearch(duration),duration);
|
||||
if (values.size()>int64_t(capacityv)) {
|
||||
Variant removed=values.pop_front();
|
||||
ordered.remove_at(ordered.bsearch(removed));
|
||||
}
|
||||
PackedByteArray after;
|
||||
const bool popped=old_count+1>int64_t(capacityv);
|
||||
if (!fast_wire || !integer_wire_update(before,values.is_typed(),old_count,duration,popped,after))
|
||||
after=UtilityFunctions::var_to_bytes(values);
|
||||
rawbytes[metric]=after; integers[metric]=after;
|
||||
const int64_t lifetime_total=the_tree_native::signed_add(int64_t(totalv),duration);
|
||||
const int64_t lifetime_count=the_tree_native::signed_add(int64_t(countv),1);
|
||||
totals[metric]=lifetime_total; counts[metric]=lifetime_count;
|
||||
const int64_t average=lifetime_count>0 ? lifetime_total/lifetime_count : 0;
|
||||
const int64_t maximum=ordered.is_empty() ? 0 : std::max<int64_t>(0,int64_t(ordered[ordered.size()-1]));
|
||||
int64_t p95=0;
|
||||
if (!ordered.is_empty()) {
|
||||
const int64_t index=std::max<int64_t>(the_tree_native::signed_add(the_tree_native::signed_add(signed_product(95,ordered.size()),99)/100,-1),0);
|
||||
p95=ordered[std::min<int64_t>(index,ordered.size()-1)];
|
||||
}
|
||||
const int64_t aggregation=aggregationv, limit=limitv;
|
||||
// Original default match is P95, including unknown aggregation values.
|
||||
const int64_t measured=aggregation==0 ? average : (aggregation==2 ? maximum : p95);
|
||||
Dictionary report;
|
||||
report[StringName("metric_id",false)]=metric;
|
||||
report[StringName("window_sample_count",false)]=values.size();
|
||||
report[StringName("lifetime_sample_count",false)]=lifetime_count;
|
||||
report[StringName("average_us",false)]=average;
|
||||
report[StringName("p95_us",false)]=p95;
|
||||
report[StringName("maximum_us",false)]=maximum;
|
||||
report[aggregation_key]=aggregation;
|
||||
report[StringName("measured_us",false)]=measured;
|
||||
report[limit_key]=limit;
|
||||
report[StringName("budget_exceeded",false)]=limit>0 && measured>limit;
|
||||
Dictionary result; result[supported]=true; result[report_key]=report; return result;
|
||||
}
|
||||
void clear_edge_cache() { cached_edges.clear(); }
|
||||
Dictionary search(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, false);
|
||||
}
|
||||
Dictionary search_cached(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, true);
|
||||
}
|
||||
// One ordered projection, with no new persistent ownership/cache. Only
|
||||
// builtin pure getters and the already guarded sole budget observer qualify.
|
||||
Dictionary project_guest_candidates(Object *runtime, const StringName &guest_id,
|
||||
int64_t open_state, int64_t degraded_state, int64_t movement_scale,
|
||||
int64_t stair_credit, int64_t orthogonal_cost, const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!builtin(n,runtime, "res://autoload/simulation_runtime.gd") || !ticks.is_valid()) return unsupported;
|
||||
Object *path = runtime->get(n.k_path_service);
|
||||
Object *elevators = runtime->get(n.k_elevators);
|
||||
Object *facilities = runtime->get(n.k_facility_service);
|
||||
Object *definitions = runtime->get(n.k_definition_registry);
|
||||
Object *inventory = runtime->get(n.k_inventory_service);
|
||||
Object *guests = runtime->get(n.k_guest_service);
|
||||
if (!builtin(n,path,"res://scripts/world/path_service.gd") ||
|
||||
!builtin(n,elevators,"res://scripts/transport/elevator_service.gd") ||
|
||||
!builtin(n,facilities,"res://scripts/facility/facility_service.gd") ||
|
||||
!builtin(n,definitions,"res://scripts/core/definition_registry.gd") ||
|
||||
!builtin(n,inventory,"res://scripts/inventory/inventory_service.gd") ||
|
||||
!builtin(n,guests,"res://scripts/guest/guest_service.gd") ||
|
||||
!static_cast<bool>(runtime->call(n.k__guest_access_batch_can_select))) return unsupported;
|
||||
Object *guest = guests->call(n.k_get_guest,guest_id);
|
||||
Array candidates;
|
||||
if (!guest) {
|
||||
Dictionary result; result[n.k_supported] = true;
|
||||
result[n.k_candidates] = candidates; return result;
|
||||
}
|
||||
if (!builtin(n,guest,"res://scripts/guest/guest_runtime.gd")) return unsupported;
|
||||
const Array initial = facilities->call(n.k_get_instances);
|
||||
// Preflight is read-only: default registry/get_instances have no public
|
||||
// error/counter effects. Custom resources retain the original whole loop.
|
||||
for (int64_t i=0;i<initial.size();++i) {
|
||||
Object *facility = initial[i];
|
||||
if (!builtin(n,facility,"res://scripts/facility/facility_instance.gd")) return unsupported;
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (definition && !builtin(n,definition,"res://scripts/data/facility_definition.gd")) return unsupported;
|
||||
}
|
||||
Array goals;
|
||||
for (int64_t i=0;i<initial.size();++i) goals.append_array(facility_access_cells(n,path,initial[i]));
|
||||
const Vector3i start = guest->get(n.k_current_cell);
|
||||
path->call(n.k_begin_guest_path_query_batch,start,goals);
|
||||
const Array ordered = facilities->call(n.k_get_instances);
|
||||
TypedArray<int64_t> layers; layers.push_back(0); layers.push_back(1);
|
||||
TypedArray<StringName> access_ids;
|
||||
for (int64_t i=0;i<ordered.size();++i) {
|
||||
Object *facility = ordered[i];
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (!definition) continue;
|
||||
const Array tags = definition->get(n.k_tags);
|
||||
if (tags.has(n.k_staff_only)) continue;
|
||||
if (tags.has(n.k_entrance) && static_cast<bool>(runtime->call(n.k__guest_has_checked_in,guest))) continue;
|
||||
bool has_inputs = true;
|
||||
const Dictionary inputs = definition->get(n.k_inventory_inputs);
|
||||
const Array items = runtime->call(n.k__sorted_name_keys,inputs);
|
||||
for (int64_t j=0;j<items.size();++j) {
|
||||
const int64_t quantity = inventory->call(n.k_available_quantity,facility->get(n.k_local_inventory_id),items[j]);
|
||||
if (quantity < static_cast<int64_t>(inputs[items[j]])) {has_inputs=false;break;}
|
||||
}
|
||||
if (!has_inputs) {
|
||||
Dictionary row;row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_inputs_available]=false;candidates.push_back(row);continue;
|
||||
}
|
||||
const Array destinations = facility_access_cells(n,path,facility);
|
||||
Dictionary route = nearest_guest_access_impl(n,path,start,destinations,facility->get(n.k_origin),layers,access_ids,ticks);
|
||||
if (static_cast<bool>(route.get(n.k_supported,false))) route.erase(n.k_supported);
|
||||
else route = Dictionary(runtime->call(n.k__find_facility_access_path,start,facility));
|
||||
const Array walk = route.get(n.k_path,Array());
|
||||
const bool plain_tag_relief = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
const Dictionary relief = plain_tag_relief ? builtin_tag_relief(n,tags) :
|
||||
Dictionary(runtime->call(n.k__need_relief_for_facility_tags,tags));
|
||||
Dictionary row;
|
||||
row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_facility_tags]=tags.duplicate();
|
||||
const int64_t state = facility->get(n.k_state);
|
||||
row[n.k_is_open]=(state==open_state || state==degraded_state);
|
||||
row[n.k_reachable]=route.get(n.k_reachable,false);
|
||||
row[n.k_travel_minutes]=guest_path_minutes(n,path,elevators,walk,movement_scale,stair_credit,orthogonal_cost);
|
||||
const Array queue = facility->get(n.k_queue_entries);
|
||||
const int64_t capacity = facility->get(n.k_capacity);
|
||||
row[n.k_expected_wait]=signed_product(queue.size(),facility->get(n.k_base_service_minutes))/std::max<int64_t>(capacity,1);
|
||||
row[n.k_estimated_total_gold]=runtime->call(n.k_get_facility_business_price,facility);
|
||||
const int64_t quality = facility->get(n.k_base_quality);
|
||||
row[n.k_expected_quality]=std::max<int64_t>(0,std::min<int64_t>(quality,100));
|
||||
row[n.k_need_relief]=relief;
|
||||
row[n.k_risk_level]=int64_t(0);row[n.k_illegal_level]=int64_t(0);
|
||||
row[n.k_group_fit]=signed_product(std::min<int64_t>(capacity,4),5);
|
||||
// Recheck at the original call position: preceding price/observer
|
||||
// callbacks must retain a replaced/custom runtime's original call.
|
||||
const bool plain_tag_category = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
row[n.k_service_category]=plain_tag_category ? Variant(builtin_tag_category(n,tags)) :
|
||||
runtime->call(n.k__service_category_for_tags,tags);
|
||||
Dictionary handle;handle[n.k_target_cell]=route.get(n.k_target_cell,guest->get(n.k_current_cell));
|
||||
handle[n.k_graph_revision]=path->get(n.k_graph_revision);
|
||||
row[n.k_path_handle]=handle;candidates.push_back(row);
|
||||
}
|
||||
path->call(n.k_end_guest_path_query_batch);
|
||||
Dictionary result;result[n.k_supported]=true;result[n.k_candidates]=candidates;return result;
|
||||
}
|
||||
private:
|
||||
// Direct translation of the two pure builtin tag methods. Fresh result,
|
||||
// original insertion/branch order, no retained definition/tag memo.
|
||||
static Dictionary builtin_tag_relief(const ProjectionNames &n,const Array &tags) {
|
||||
Dictionary relief;
|
||||
relief[n.k_tagproj_hunger]=int64_t(0);relief[n.k_tagproj_sleep]=int64_t(0);
|
||||
relief[n.k_tagproj_hygiene]=int64_t(0);relief[n.k_tagproj_medical]=int64_t(0);
|
||||
relief[n.k_tagproj_leisure]=int64_t(0);relief[n.k_tagproj_intimacy]=int64_t(0);
|
||||
if(tags.has(n.k_tagproj_dining)) {relief[n.k_tagproj_hunger]=int64_t(80);relief[n.k_tagproj_leisure]=int64_t(10);}
|
||||
if(tags.has(n.k_tagproj_lodging)) {relief[n.k_tagproj_sleep]=int64_t(90);relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),10);}
|
||||
if(tags.has(n.k_tagproj_social)||tags.has(n.k_tagproj_leisure)) relief[n.k_tagproj_leisure]=std::max<int64_t>(int64_t(relief[n.k_tagproj_leisure]),40);
|
||||
if(tags.has(n.k_tagproj_hygiene)||tags.has(n.k_tagproj_bath)) relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),80);
|
||||
if(tags.has(n.k_tagproj_medical)) relief[n.k_tagproj_medical]=int64_t(90);
|
||||
else if(tags.has(n.k_tagproj_wellness)) relief[n.k_tagproj_medical]=std::max<int64_t>(int64_t(relief[n.k_tagproj_medical]),20);
|
||||
if(tags.has(n.k_tagproj_adult_only)||tags.has(n.k_tagproj_restricted_service)) relief[n.k_tagproj_intimacy]=int64_t(80);
|
||||
return relief;
|
||||
}
|
||||
static StringName builtin_tag_category(const ProjectionNames &n,const Array &tags) {
|
||||
if(tags.has(n.k_tagproj_dining))return n.k_tagproj_food;
|
||||
if(tags.has(n.k_tagproj_lodging))return n.k_tagproj_lodging;
|
||||
if(tags.has(n.k_tagproj_social))return n.k_tagproj_leisure;
|
||||
return n.k_tagproj_service;
|
||||
}
|
||||
static bool builtin(const ProjectionNames &n,Object *object,const char *resource) {
|
||||
if (!object) return false;Object *script=object->get_script();
|
||||
return script && String(script->get(n.k_resource_path))==resource;
|
||||
}
|
||||
// Exact builtin cache-key spelling. No normalized/reordered profile and no
|
||||
// persistent cache; String lengths count Unicode codepoints as GDScript does.
|
||||
static String cache_key_suffix(const ProjectionNames &n,Object *path, const TypedArray<int64_t> &layers,
|
||||
const TypedArray<StringName> &access) {
|
||||
String layer_text, access_text;
|
||||
for (int64_t i=0; i<layers.size(); ++i) {
|
||||
if (i) layer_text += ",";
|
||||
layer_text += String::num_int64(static_cast<int64_t>(layers[i]));
|
||||
}
|
||||
for (int64_t i=0; i<access.size(); ++i) {
|
||||
if (i) access_text += ",";
|
||||
const String value = String(static_cast<StringName>(access[i]));
|
||||
access_text += String::num_int64(value.length()) + ":" + value;
|
||||
}
|
||||
return String("|L:") + layer_text + "|A:" + access_text + "|R:" +
|
||||
String::num_int64(static_cast<int64_t>(path->get(n.k_graph_revision)));
|
||||
}
|
||||
static String cache_key_with_suffix(const Vector3i &start, const Vector3i &goal,
|
||||
const String &suffix) {
|
||||
return String::num_int64(start.x) + "," + String::num_int64(start.y) + "," +
|
||||
String::num_int64(start.z) + ">" + String::num_int64(goal.x) + "," +
|
||||
String::num_int64(goal.y) + "," + String::num_int64(goal.z) + suffix;
|
||||
}
|
||||
static int64_t signed_product(int64_t a,int64_t b) {
|
||||
const uint64_t product=static_cast<uint64_t>(a)*static_cast<uint64_t>(b);
|
||||
if (product<=static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) return static_cast<int64_t>(product);
|
||||
return std::numeric_limits<int64_t>::min()+static_cast<int64_t>(product-(uint64_t(1)<<63));
|
||||
}
|
||||
static Array facility_access_cells(const ProjectionNames &n,Object *path,Object *facility) {
|
||||
const Dictionary passable=path->get(n.k__passable_cells);
|
||||
Array occupied=Array(facility->get(n.k_occupied_cells)).duplicate();
|
||||
if (occupied.is_empty()) occupied.push_back(facility->get(n.k_origin));
|
||||
Dictionary unique;
|
||||
for (int64_t i=0;i<occupied.size();++i) {
|
||||
const Vector3i cell=occupied[i];
|
||||
for (int32_t dx : {-1,1}) {
|
||||
// Match GDScript's Vector3i conversion after int64 addition.
|
||||
const Vector3i candidate(int64_t(cell.x)+dx,cell.y,cell.z);
|
||||
if (passable.has(candidate)) unique[candidate]=true;
|
||||
}
|
||||
}
|
||||
const Vector3i origin=facility->get(n.k_origin);if(passable.has(origin)) unique[origin]=true;
|
||||
const Array keys=unique.keys();std::vector<Vector3i> sorted;sorted.reserve(keys.size());
|
||||
for (int64_t i=0;i<keys.size();++i) sorted.push_back(keys[i]);
|
||||
std::sort(sorted.begin(),sorted.end(),[](const Vector3i &a,const Vector3i &b){
|
||||
if(a.z!=b.z)return a.z<b.z;if(a.y!=b.y)return a.y<b.y;return a.x<b.x;
|
||||
});
|
||||
Array result;for(const Vector3i &cell:sorted)result.push_back(cell);return result;
|
||||
}
|
||||
static int64_t guest_path_minutes(const ProjectionNames &n,Object *path,Object *elevators,const Array &route,
|
||||
int64_t movement_scale,int64_t stair_credit,int64_t orthogonal_cost) {
|
||||
Dictionary memo=path->get(n.k__guest_step_connector_memo);
|
||||
// Route-local only. Reuse an ownership result only after an actual
|
||||
// BOOL return and a typed cached step; no script callback occurs on a
|
||||
// repeated typed memo hit. A miss/conversion/custom script keeps calls.
|
||||
const bool builtin_pair = builtin(n,path,"res://scripts/world/path_service.gd") &&
|
||||
builtin(n,elevators,"res://scripts/transport/elevator_service.gd");
|
||||
bool ownership_valid = false;
|
||||
bool ownership_value = false;
|
||||
StringName ownership_connector;
|
||||
for(int64_t i=1;i<route.size();++i) {
|
||||
const Vector3i from=route[i-1],to=route[i];
|
||||
const Dictionary destinations=memo.get(from,Dictionary());
|
||||
StringName connector;
|
||||
const bool memo_hit = destinations.has(to);
|
||||
const bool typed_memo_hit = memo_hit && destinations[to].get_type() == Variant::STRING_NAME;
|
||||
if(memo_hit)connector=destinations[to];
|
||||
else {TypedArray<StringName> access;connector=path->call(n.k_get_step_connector,from,to,access);}
|
||||
bool special;
|
||||
if (builtin_pair && typed_memo_hit && ownership_valid && connector == ownership_connector) {
|
||||
special = ownership_value;
|
||||
} else {
|
||||
const Variant owned = elevators->call(n.k_owns_connector,connector);
|
||||
special = static_cast<bool>(owned);
|
||||
ownership_valid = builtin_pair && typed_memo_hit && owned.get_type() == Variant::BOOL;
|
||||
ownership_value = special;
|
||||
ownership_connector = connector;
|
||||
}
|
||||
if(!special && String(connector).begins_with("infra.connector:") && from.x==to.x && from.z==to.z &&
|
||||
std::abs(int64_t(from.y)-int64_t(to.y))==1) {
|
||||
const Dictionary detail=path->call(n.k_get_connector,connector);
|
||||
special=detail.get(n.k_movement_type,StringName())==Variant(n.k_CONNECTOR) &&
|
||||
static_cast<int64_t>(detail.get(n.k_cost,int64_t(0)))==150 && stair_credit>movement_scale;
|
||||
}
|
||||
if(special) {
|
||||
const int64_t cost=path->call(n.k_calculate_guest_batch_path_cost,route[0],route[route.size()-1],route);
|
||||
return static_cast<int64_t>(std::ceil(static_cast<double>(cost)/static_cast<double>(orthogonal_cost)));
|
||||
}
|
||||
}
|
||||
return std::max<int64_t>(route.size()-1,0);
|
||||
}
|
||||
public:
|
||||
|
||||
// Stateless translation of the builtin nearest access query. The caller
|
||||
// supplies the existing typed default profile and the real Time callable.
|
||||
// Public cache publication, failure diagnostics and fallback stay ordered.
|
||||
Dictionary nearest_guest_access(Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
return nearest_guest_access_impl(n,path,start,goals,fallback_target,layers,access,ticks);
|
||||
}
|
||||
private:
|
||||
Dictionary nearest_guest_access_impl(const ProjectionNames &n,Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!path || !ticks.is_valid() || goals.is_empty()) return unsupported;
|
||||
Object *script = path->get_script();
|
||||
if (!script || String(script->get(n.k_resource_path)) != "res://scripts/world/path_service.gd") return unsupported;
|
||||
Dictionary batch = path->get(n.k__query_batch);
|
||||
if (batch.is_empty() || start != static_cast<Vector3i>(batch[n.k_start])) return unsupported;
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start) || blocked(n,path, start) || !layers.has(start.z)) return unsupported;
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
// Exact builtin validation loop has no callback before ticks.call().
|
||||
// Build the unchanged profile/revision suffix lazily at the first key.
|
||||
String key_suffix;
|
||||
bool key_suffix_ready = false;
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
if (goals[j].get_type() != Variant::VECTOR3I) return unsupported;
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell)) {
|
||||
const bool searchable = passable.has(cell) && layers.has(cell.z) && !blocked(n,path, cell);
|
||||
if (searchable && (start.z == cell.z || static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, cell, layers, access)))) return unsupported;
|
||||
}
|
||||
if (!key_suffix_ready) {
|
||||
key_suffix = cache_key_suffix(n,path, layers, access);
|
||||
key_suffix_ready = true;
|
||||
}
|
||||
const String key = cache_key_with_suffix(start, cell, key_suffix);
|
||||
if (cache.has(key)) return unsupported;
|
||||
}
|
||||
const int64_t started = ticks.call();
|
||||
if (!static_cast<bool>(batch[n.k_ready])) {
|
||||
batch[n.k_tree] = path->call(n.k__build_guest_query_tree_compact, start, targets, layers, access);
|
||||
batch[n.k_arithmetic_wrapped] = path->get(n.k__guest_tree_arithmetic_wrapped);
|
||||
batch[n.k_ready] = true;
|
||||
}
|
||||
const Dictionary tree = batch[n.k_tree];
|
||||
if (!static_cast<bool>(tree.get(n.k_usable, false)) || static_cast<bool>(batch.get(n.k_arithmetic_wrapped, false))) return unsupported;
|
||||
Vector3i best_cell = fallback_target;
|
||||
bool found = false;
|
||||
int64_t best_cost = 0;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell) || !settled.has(cell)) continue;
|
||||
if (!tree_has_cost(n,tree, cell)) return unsupported;
|
||||
const int64_t cost = tree_cost(n,tree, cell);
|
||||
if (!found || cost < best_cost) {
|
||||
found = true;
|
||||
best_cost = cost;
|
||||
best_cell = cell;
|
||||
}
|
||||
}
|
||||
TypedArray<Vector3i> best_path;
|
||||
if (found) {
|
||||
best_path = resolve_ready_guest_point(n,path, start, best_cell, tree, layers, access);
|
||||
if (best_path.is_empty()) return unsupported;
|
||||
}
|
||||
if (!goals.is_empty() && (!found || goals[goals.size() - 1] != Variant(best_cell))) {
|
||||
resolve_ready_guest_point(n,path, start, goals[goals.size() - 1], tree, layers, access);
|
||||
}
|
||||
const int64_t elapsed = the_tree_native::signed_add(static_cast<int64_t>(ticks.call()), the_tree_native::signed_add(~started, 1));
|
||||
path->emit_signal(n.k_path_query_measured, std::max<int64_t>(elapsed, 0));
|
||||
Dictionary result;
|
||||
result[n.k_supported] = true;
|
||||
result[n.k_reachable] = found;
|
||||
result[n.k_path] = best_path;
|
||||
result[n.k_target_cell] = best_cell;
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
static bool blocked(const ProjectionNames &n,Object *path, const Vector3i &cell) {
|
||||
const Dictionary traffic_map = path->get(n.k__cell_traffic);
|
||||
const Dictionary traffic = traffic_map.get(cell, Dictionary());
|
||||
return !traffic.is_empty() && static_cast<bool>(traffic[n.k_temporary_block]);
|
||||
}
|
||||
static bool tree_has_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return values.has(cell);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
if (!indices.has(cell)) return false;
|
||||
const int64_t index = indices[cell];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return index >= 0 && index < values.size();
|
||||
}
|
||||
static int64_t tree_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return static_cast<int64_t>(values[cell]);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return values[static_cast<int64_t>(indices[cell])];
|
||||
}
|
||||
static TypedArray<Vector3i> resolve_ready_guest_point(const ProjectionNames &n,Object *path,
|
||||
const Vector3i &start, const Vector3i &goal, const Dictionary &tree,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access) {
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start)) return path->call(n.k__route_failure, n.k_START_NOT_PASSABLE);
|
||||
if (!passable.has(goal)) return path->call(n.k__route_failure, n.k_GOAL_NOT_PASSABLE);
|
||||
if (!layers.has(start.z) || !layers.has(goal.z)) return path->call(n.k__route_failure, n.k_ACCESS_LAYER_DENIED);
|
||||
if (blocked(n,path, start)) return path->call(n.k__route_failure, n.k_START_TEMPORARILY_BLOCKED);
|
||||
if (blocked(n,path, goal)) return path->call(n.k__route_failure, n.k_GOAL_TEMPORARILY_BLOCKED);
|
||||
// ticks/tree callbacks may have changed the script or revision. Recheck
|
||||
// at this original call position and never reuse the pre-tick suffix.
|
||||
const String key = builtin(n,path,"res://scripts/world/path_service.gd") ?
|
||||
cache_key_with_suffix(start, goal, cache_key_suffix(n,path, layers, access)) :
|
||||
String(path->call(n.k__cache_key, start, goal, layers, access));
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
const Dictionary batch = path->get(n.k__query_batch);
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
if (cache.has(key) || !targets.has(goal)) return path->call(n.k__find_path_internal, start, goal);
|
||||
path->set(n.k_cache_misses, the_tree_native::signed_add(static_cast<int64_t>(path->get(n.k_cache_misses)), 1));
|
||||
if (start.z != goal.z && !static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, goal, layers, access))) return path->call(n.k__route_failure, n.k_GOAL_UNREACHABLE);
|
||||
TypedArray<Vector3i> route;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
if (start == goal) route.push_back(start);
|
||||
else if (settled.has(goal)) route = Array(path->call(n.k__guest_tree_reconstruct_path, tree, goal));
|
||||
if (route.is_empty()) return path->call(n.k__route_failure, path->call(n.k__diagnose_no_route_reason, start, goal, layers, access));
|
||||
cache[key] = path->call(n.k__copy_path, route);
|
||||
path->set(n.k_last_no_route_reason, n.k_OK);
|
||||
path->set(n.k_last_path_cost, tree_cost(n,tree, goal));
|
||||
path->set(n.k_last_error, String(""));
|
||||
return route;
|
||||
}
|
||||
public:
|
||||
private:
|
||||
// One existing builtin provider operation, with no new value cache. Only
|
||||
// connector-free WALK rows qualify; unsupported cases call the old provider.
|
||||
// All work stays private until the original final shared[current] assignment.
|
||||
struct WalkEdgeNames {
|
||||
const StringName resource_path{"resource_path",false}, constants_method{"get_script_constant_map",false};
|
||||
const StringName front{"FRONT",false}, back{"BACK",false}, walk{"MOVEMENT_WALK",false};
|
||||
const StringName base{"ORTHOGONAL_MOVE_COST",false}, congestion{"CONGESTION_COST_PER_EXCESS",false};
|
||||
const StringName workspace{"_guest_tree_workspace",false}, edges{"edges",false}, indices{"indices",false};
|
||||
const StringName adjacency{"_adjacency",false}, traffic{"_cell_traffic",false}, vertical{"_vertical_walk_requires_connector",false};
|
||||
const StringName to{"to",false}, connector{"connector_id",false}, access{"access_id",false}, movement{"movement_type",false};
|
||||
const StringName base_cost{"base_cost",false}, cost{"cost",false}, temporary{"temporary_block",false};
|
||||
const StringName dynamic{"dynamic_cost",false}, density{"current_density",false}, capacity{"base_capacity",false};
|
||||
const StringName provider{"_guest_tree_shared_edges",false};
|
||||
};
|
||||
struct WalkEdgeContext {
|
||||
Object *path=nullptr;
|
||||
int64_t front=0, back=0, base=0, congestion=0;
|
||||
StringName walk;
|
||||
};
|
||||
static bool walk_edge_context(const WalkEdgeNames &n,Object *path,const Array &layers,
|
||||
const Array &access,WalkEdgeContext &context) {
|
||||
if (!path || layers.size()!=2 || !access.is_empty() ||
|
||||
layers[0].get_type()!=Variant::INT || layers[1].get_type()!=Variant::INT) return false;
|
||||
Object *script=path->get_script();
|
||||
if (!script || String(script->get(n.resource_path))!="res://scripts/world/path_service.gd" ||
|
||||
!script->has_method(n.constants_method)) return false;
|
||||
const Variant value=script->call(n.constants_method);
|
||||
if (value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary constants=value;
|
||||
for (const StringName &key : {n.front,n.back,n.base,n.congestion})
|
||||
if (constants.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
if (constants.get(n.walk,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
int64_t(layers[0])!=int64_t(constants[n.front]) ||
|
||||
int64_t(layers[1])!=int64_t(constants[n.back])) return false;
|
||||
context.path=path;context.front=constants[n.front];context.back=constants[n.back];
|
||||
context.base=constants[n.base];context.congestion=constants[n.congestion];context.walk=constants[n.walk];
|
||||
return true;
|
||||
}
|
||||
static bool builtin_walk_edge_rows(const WalkEdgeNames &n,const WalkEdgeContext &context,
|
||||
const Vector3i ¤t,Array &rows) {
|
||||
if (!context.path) return false;
|
||||
const Variant workspace_value=context.path->get(n.workspace);
|
||||
if (workspace_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary workspace=workspace_value;
|
||||
const Variant shared_value=workspace.get(n.edges,Variant());
|
||||
if (shared_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
Dictionary shared=shared_value;
|
||||
// Original cached-row branch precedes readonly and all other state reads.
|
||||
if (shared.has(current)) {
|
||||
const Variant cached=shared[current];
|
||||
if (cached.get_type()!=Variant::ARRAY) return false;
|
||||
rows=cached;return true;
|
||||
}
|
||||
if (shared.is_read_only()) return false;
|
||||
const Variant indices_value=workspace.get(n.indices,Variant()), adjacency_value=context.path->get(n.adjacency);
|
||||
const Variant traffic_value=context.path->get(n.traffic), vertical_value=context.path->get(n.vertical);
|
||||
if (indices_value.get_type()!=Variant::DICTIONARY || adjacency_value.get_type()!=Variant::DICTIONARY ||
|
||||
traffic_value.get_type()!=Variant::DICTIONARY || vertical_value.get_type()!=Variant::BOOL) return false;
|
||||
const Dictionary indices=indices_value, adjacency=adjacency_value, traffic=traffic_value;
|
||||
const Variant raw_value=adjacency.get(current,Array());
|
||||
if (raw_value.get_type()!=Variant::ARRAY) return false;
|
||||
const Array raw=raw_value;Array result;
|
||||
for (int64_t i=0;i<raw.size();++i) {
|
||||
if (raw[i].get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary edge=raw[i];
|
||||
if (edge.get(n.to,Variant()).get_type()!=Variant::VECTOR3I) return false;
|
||||
const Vector3i neighbor=edge[n.to];
|
||||
if (neighbor.z!=context.front && neighbor.z!=context.back) continue;
|
||||
const Variant row_value=traffic.get(neighbor,Dictionary());
|
||||
if (row_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary row=row_value;
|
||||
if (!row.is_empty()) {
|
||||
if (row.get(n.temporary,Variant()).get_type()!=Variant::BOOL) return false;
|
||||
if (bool(row[n.temporary])) continue;
|
||||
}
|
||||
// Preserve old method for every connector/access/other movement row.
|
||||
if (edge.get(n.connector,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.access,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.movement,Variant()).get_type()!=Variant::STRING_NAME) return false;
|
||||
const StringName connector=edge[n.connector], access=edge[n.access], movement=edge[n.movement];
|
||||
if (!connector.is_empty() || !access.is_empty() || movement!=context.walk) return false;
|
||||
if (bool(vertical_value) && current.y!=neighbor.y) continue;
|
||||
const Variant base=edge.get(n.base_cost,edge.get(n.cost,context.base));
|
||||
if (base.get_type()!=Variant::INT) return false;
|
||||
int64_t dynamic=0;
|
||||
if (!row.is_empty()) {
|
||||
for (const StringName &key : {n.dynamic,n.density,n.capacity})
|
||||
if (row.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
const int64_t excess=the_tree_native::signed_add(int64_t(row[n.density]),
|
||||
the_tree_native::signed_add(~int64_t(row[n.capacity]),1));
|
||||
dynamic=the_tree_native::signed_add(int64_t(row[n.dynamic]),
|
||||
signed_product(excess>0?excess:0,context.congestion));
|
||||
}
|
||||
const int64_t cost=the_tree_native::signed_add(int64_t(base),dynamic);
|
||||
Array pair;pair.push_back(neighbor);pair.push_back(cost);result.push_back(pair);
|
||||
// Same short circuit and first invalid prefix as the original method.
|
||||
if (cost<=0 || !indices.has(neighbor)) break;
|
||||
}
|
||||
shared[current]=result;rows=result;return true;
|
||||
}
|
||||
public:
|
||||
Dictionary guest_tree_edge_rows(Object *path,const Vector3i ¤t,const Array &layers,const Array &access) {
|
||||
const WalkEdgeNames names;WalkEdgeContext context;Array rows;
|
||||
Dictionary result;const StringName supported{"supported",false}, row_key{"rows",false};
|
||||
result[supported]=false;
|
||||
if (!walk_edge_context(names,path,layers,access,context) ||
|
||||
!builtin_walk_edge_rows(names,context,current,rows)) return result;
|
||||
result[supported]=true;result[row_key]=rows;return result;
|
||||
}
|
||||
private:
|
||||
Dictionary search_impl(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider, bool use_edge_cache) {
|
||||
Dictionary output;
|
||||
output[StringName("status")] = String("unusable");
|
||||
if (start_index < 0 || start_index >= cells.size() || !edge_provider.is_valid()) return output;
|
||||
try {
|
||||
std::vector<I> goal_indices;
|
||||
const Array goal_keys = goals.keys();
|
||||
for (I j = 0; j < goal_keys.size(); ++j)
|
||||
goal_indices.push_back(static_cast<I>(indices.get(goal_keys[j], I(-1))));
|
||||
Array arguments;
|
||||
arguments.resize(1);
|
||||
const WalkEdgeNames walk_names;WalkEdgeContext walk_context;
|
||||
if (edge_provider.get_method()==walk_names.provider && edge_provider.get_argument_count()==1 &&
|
||||
edge_provider.get_bound_arguments_count()==2 && edge_provider.get_unbound_arguments_count()==0) {
|
||||
const Array bound=edge_provider.get_bound_arguments();
|
||||
if (bound.size()==2 && bound[0].get_type()==Variant::ARRAY && bound[1].get_type()==Variant::ARRAY)
|
||||
walk_edge_context(walk_names,edge_provider.get_object(),bound[0],bound[1],walk_context);
|
||||
}
|
||||
// Call the original bound Godot edge method only when the search expands a node.
|
||||
// Preserve its lazy cache population, filter ordering and first invalid row.
|
||||
auto original_edges = [&](I current) {
|
||||
arguments[0] = cells[current];
|
||||
Array fused;bool fused_supported=false;
|
||||
if (cells[current].get_type()==Variant::VECTOR3I) {
|
||||
const Vector3i cell=cells[current];
|
||||
fused_supported=builtin_walk_edge_rows(walk_names,walk_context,cell,fused);
|
||||
}
|
||||
const Variant value = fused_supported ? Variant(fused) : edge_provider.callv(arguments);
|
||||
if (value.get_type() != Variant::ARRAY) return std::vector<Edge>{{-1, 0}};
|
||||
const Array rows = value;
|
||||
std::vector<Edge> result;
|
||||
result.reserve(rows.size());
|
||||
for (I j = 0; j < rows.size(); ++j) {
|
||||
if (rows[j].get_type() != Variant::ARRAY) { result.push_back({-1, 0}); break; }
|
||||
const Array row = rows[j];
|
||||
if (row.size() != 2 || row[1].get_type() != Variant::INT) {
|
||||
result.push_back({-1, 0}); break;
|
||||
}
|
||||
const I cost = static_cast<I>(row[1]);
|
||||
// Nonpositive cost is rejected before requesting the neighbor index.
|
||||
if (cost <= 0) { result.push_back({-1, cost}); break; }
|
||||
const I neighbor = static_cast<I>(indices.get(row[0], I(-1)));
|
||||
result.push_back({neighbor, cost});
|
||||
if (neighbor < 0) break;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto edges = [&](I current) {
|
||||
if (!use_edge_cache) return original_edges(current);
|
||||
const auto found = cached_edges.find(current);
|
||||
if (found != cached_edges.end()) return found->second;
|
||||
// Populate only when the old algorithm expands this same node.
|
||||
// Retain original provider validation and first-invalid prefix.
|
||||
auto converted = original_edges(current);
|
||||
cached_edges.emplace(current, converted);
|
||||
return converted;
|
||||
};
|
||||
const auto result = the_tree_native::search(cells.size(), start_index, goal_indices, edges);
|
||||
output[StringName("status")] = String(result.status.c_str());
|
||||
if (result.status != "usable") return output;
|
||||
PackedInt64Array costs, parents;
|
||||
if (costs.resize(result.costs.size()) != OK || parents.resize(result.parents.size()) != OK) {
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
for (I j = 0; j < cells.size(); ++j) { costs.set(j, result.costs[j]); parents.set(j, result.parents[j]); }
|
||||
TypedArray<int64_t> discovered, parent_discovered;
|
||||
for (I j : result.discovered) discovered.push_back(j);
|
||||
for (I j : result.parent_discovered) parent_discovered.push_back(j);
|
||||
Dictionary settled;
|
||||
for (I j : result.settled) settled[cells[j]] = true;
|
||||
output[StringName("cost_values")] = costs;
|
||||
output[StringName("parent_indices")] = parents;
|
||||
output[StringName("discovered")] = discovered;
|
||||
output[StringName("parent_discovered")] = parent_discovered;
|
||||
output[StringName("settled")] = settled;
|
||||
return output;
|
||||
} catch (const std::exception &) {
|
||||
output = Dictionary();
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void initialize_tree_numeric_core(ModuleInitializationLevel level) {
|
||||
if (level == MODULE_INITIALIZATION_LEVEL_SCENE) GDREGISTER_CLASS(TheTreeNumericCore);
|
||||
}
|
||||
void uninitialize_tree_numeric_core(ModuleInitializationLevel) {}
|
||||
|
||||
extern "C" {
|
||||
GDExtensionBool GDE_EXPORT tree_numeric_core_init(GDExtensionInterfaceGetProcAddress get_proc,
|
||||
GDExtensionClassLibraryPtr library, GDExtensionInitialization *initialization) {
|
||||
GDExtensionBinding::InitObject init(get_proc, library, initialization);
|
||||
init.register_initializer(initialize_tree_numeric_core);
|
||||
init.register_terminator(uninitialize_tree_numeric_core);
|
||||
init.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
return init.init();
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"plan_id": "P23-02",
|
||||
"candidate": "r285-r02",
|
||||
"base_ledger_commit": "21d89b988d360269321d79a23f011858777e2fbf",
|
||||
"scope": "Development source and finite static evidence checkpoint, not production adoption or acceptance",
|
||||
"static_review_accepted": true,
|
||||
"build_preexecution_review_accepted": true,
|
||||
"build_executed": false,
|
||||
"engine_executed": false,
|
||||
"performance_accepted": false,
|
||||
"candidate_adopted": false,
|
||||
"unit_accepted": false,
|
||||
"completed_units_added": 0,
|
||||
"files": [
|
||||
{
|
||||
"checkpoint_path": "source/tree_numeric_core.cpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp",
|
||||
"bytes": 59396,
|
||||
"sha256": "75bc2f659f62889fccac5990461f5b51e8b87f30ca0791e4a4c542d37a7c5236"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "source/tree_core.hpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "source/input.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/input.json",
|
||||
"bytes": 4325,
|
||||
"sha256": "38fb6adc0ec74c0f37631da3f1ddecf185879be9655574a920f1c12566beaa8f"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "parent-r274/tree_numeric_core.cpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "parent-r274/tree_core.hpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "history-r285/tree_numeric_core.cpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285/source/tree_numeric_core.cpp",
|
||||
"bytes": 59281,
|
||||
"sha256": "2edd9d6d782cd4072272f0c24b50ce74691899c5a4bbc9ccc946a991217d814a"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "history-r285/static-review.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285.json",
|
||||
"bytes": 9547,
|
||||
"sha256": "c3f3525ee6b172bcbbc79838d49beac137d2dbd86deb61cdbefc1a921e743f06"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "evidence/static-review-r285-r02.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-independent-review-r285-r02.json",
|
||||
"bytes": 10950,
|
||||
"sha256": "bffbdcb491ba0aa68eeac764228c04418d2ef3834624cb1fcb98db773e8369fd"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "evidence/static-root-verification-r285-r02.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-static-root-verification-r285-r02.json",
|
||||
"bytes": 4849,
|
||||
"sha256": "27b0b957737d79a3047f2b2a8fa936ac0ec368414d61bb6f097a524a3fd27ead"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "evidence/build-preexecution-review-r286.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-preexecution-independent-review-r286.json",
|
||||
"bytes": 19698,
|
||||
"sha256": "f3fab769c0baeacbec945e745debaf16c78dfed134bffa33af4683d1779ec72f"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "reference/marshalls.cpp",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "reference/source.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "dependencies/performance_budget.candidate.gd",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "preparation/prepare-r285.py",
|
||||
"original_path": "/private/tmp/thetree-prepare-integer-window-codec-r285.py",
|
||||
"bytes": 8488,
|
||||
"sha256": "ef011afc1f7a8d590330ab9f24bbd973b5bdda6e1923675f55c4f4c83ed9d93b"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "preparation/prepare-r285-r02.py",
|
||||
"original_path": "/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py",
|
||||
"bytes": 8607,
|
||||
"sha256": "7316b5b10bc65f7e286b66940767175f1f6b07182b84c8c2b326907bc668f9b5"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "preparation/build-r286.py",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286.py",
|
||||
"bytes": 16592,
|
||||
"sha256": "14bdaa2dcfab62f594fcb95a9ead5bc6defbc35e727fbb0de46a7494af780e3a"
|
||||
},
|
||||
{
|
||||
"checkpoint_path": "preparation/build-preview-r286.json",
|
||||
"original_path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-preview-r286.json",
|
||||
"bytes": 3402,
|
||||
"sha256": "cba922a40684eaf3206c17ac8eaa588b04015a9f09d0dbc5f79a16bf7f4159b2"
|
||||
}
|
||||
]
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
// Standalone numeric prototype; not connected to Godot or production.
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace the_tree_native {
|
||||
using I = std::int64_t;
|
||||
struct Edge { I neighbor, cost; };
|
||||
struct Result {
|
||||
std::string status = "usable";
|
||||
std::vector<I> costs, parents, discovered, parent_discovered, settled;
|
||||
};
|
||||
I signed_add(I a, I b) {
|
||||
const auto sum = static_cast<std::uint64_t>(a) + static_cast<std::uint64_t>(b);
|
||||
if (sum <= static_cast<std::uint64_t>(std::numeric_limits<I>::max())) return static_cast<I>(sum);
|
||||
return std::numeric_limits<I>::min() + static_cast<I>(sum - (std::uint64_t(1) << 63));
|
||||
}
|
||||
template <typename EdgeProvider>
|
||||
Result search(I n, I start, const std::vector<I>& goals, EdgeProvider&& edge_provider) {
|
||||
Result r;
|
||||
if (start < 0 || start >= n) { r.status = "unusable"; return r; }
|
||||
r.costs.assign(n, std::numeric_limits<I>::max());
|
||||
r.parents.assign(n, -1);
|
||||
std::vector<unsigned char> finalized(n, 0), pending(n, 0);
|
||||
bool unknown_goal = false;
|
||||
I remaining = 0;
|
||||
for (I goal : goals) {
|
||||
if (goal < 0 || goal >= n) { unknown_goal = true; continue; }
|
||||
if (!pending[goal]) { pending[goal] = 1; ++remaining; }
|
||||
}
|
||||
r.costs[start] = 0; r.discovered.push_back(start);
|
||||
std::map<I, std::vector<I>> frontier;
|
||||
frontier[0].push_back(start);
|
||||
while (!frontier.empty()) {
|
||||
auto it = frontier.begin();
|
||||
const I entry_cost = it->first;
|
||||
auto active = std::move(it->second);
|
||||
frontier.erase(it);
|
||||
std::sort(active.begin(), active.end());
|
||||
for (I current : active) {
|
||||
if (finalized[current] || entry_cost != r.costs[current]) continue;
|
||||
finalized[current] = 1;
|
||||
r.settled.push_back(current);
|
||||
if (pending[current]) { pending[current] = 0; --remaining; }
|
||||
if (!remaining && !unknown_goal) return r;
|
||||
for (const Edge& edge : edge_provider(current)) {
|
||||
if (edge.cost <= 0) { r.status = "unusable"; return r; }
|
||||
const I neighbor = edge.neighbor;
|
||||
if (neighbor < 0 || neighbor >= n) { r.status = "unusable"; return r; }
|
||||
const I cost = signed_add(r.costs[current], edge.cost);
|
||||
if (cost < r.costs[current]) { r.status = "wrapped"; return r; }
|
||||
const I known = r.costs[neighbor];
|
||||
const bool better_parent = cost == known && r.parents[neighbor] >= 0 && current < r.parents[neighbor];
|
||||
if (cost > known || (cost == known && !better_parent)) continue;
|
||||
if (r.parents[neighbor] < 0) {
|
||||
r.parent_discovered.push_back(neighbor);
|
||||
if (neighbor != start) r.discovered.push_back(neighbor);
|
||||
}
|
||||
r.parents[neighbor] = current;
|
||||
r.costs[neighbor] = cost;
|
||||
if (finalized[neighbor]) r.settled.erase(std::remove(r.settled.begin(), r.settled.end(), neighbor), r.settled.end());
|
||||
finalized[neighbor] = 0;
|
||||
frontier[cost].push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
} // namespace the_tree_native
|
||||
+823
@@ -0,0 +1,823 @@
|
||||
// Isolated, uncompiled bridge prototype. No production registration or integration yet.
|
||||
#include "tree_core.hpp"
|
||||
#include <cmath>
|
||||
#include <godot_cpp/classes/ref_counted.hpp>
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/variant/vector3i.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
#include <godot_cpp/variant/array.hpp>
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
#include <godot_cpp/variant/packed_int64_array.hpp>
|
||||
#include <godot_cpp/variant/typed_array.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
using namespace godot;
|
||||
using the_tree_native::I;
|
||||
using the_tree_native::Edge;
|
||||
|
||||
class TheTreeNumericCore : public RefCounted {
|
||||
GDCLASS(TheTreeNumericCore, RefCounted)
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("performance_record_sample", "owner", "metric_id", "duration_us"), &TheTreeNumericCore::performance_record_sample);
|
||||
ClassDB::bind_method(D_METHOD("guest_tree_edge_rows", "path", "current", "layers", "access"), &TheTreeNumericCore::guest_tree_edge_rows);
|
||||
ClassDB::bind_method(D_METHOD("search", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search);
|
||||
ClassDB::bind_method(D_METHOD("search_cached", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search_cached);
|
||||
ClassDB::bind_method(D_METHOD("clear_edge_cache"), &TheTreeNumericCore::clear_edge_cache);
|
||||
ClassDB::bind_method(D_METHOD("nearest_guest_access", "path", "start", "goals", "fallback_target", "layers", "access", "ticks"), &TheTreeNumericCore::nearest_guest_access);
|
||||
ClassDB::bind_method(D_METHOD("project_guest_candidates", "runtime", "guest_id", "open_state", "degraded_state", "movement_scale", "stair_credit", "orthogonal_cost", "ticks"), &TheTreeNumericCore::project_guest_candidates);
|
||||
}
|
||||
private:
|
||||
// Cache belongs to this instance and one caller-owned graph workspace.
|
||||
// No reuse after cells, indices, filters or edge values change: the caller
|
||||
// must clear it or release this core. The original search never uses it.
|
||||
std::map<I, std::vector<Edge>> cached_edges;
|
||||
// Per-public-call immutable names. No static/global lifetime, cached
|
||||
// object values, script dispatch result or profile; recursive calls own
|
||||
// separate scopes. Destruction precedes leaving the engine call.
|
||||
struct ProjectionNames {
|
||||
const StringName k_ACCESS_LAYER_DENIED{"ACCESS_LAYER_DENIED",false};
|
||||
const StringName k_CONNECTOR{"CONNECTOR",false};
|
||||
const StringName k_GOAL_NOT_PASSABLE{"GOAL_NOT_PASSABLE",false};
|
||||
const StringName k_GOAL_TEMPORARILY_BLOCKED{"GOAL_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k_GOAL_UNREACHABLE{"GOAL_UNREACHABLE",false};
|
||||
const StringName k_OK{"OK",false};
|
||||
const StringName k_START_NOT_PASSABLE{"START_NOT_PASSABLE",false};
|
||||
const StringName k_START_TEMPORARILY_BLOCKED{"START_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k__build_guest_query_tree_compact{"_build_guest_query_tree_compact",false};
|
||||
const StringName k__cache_key{"_cache_key",false};
|
||||
const StringName k__cell_traffic{"_cell_traffic",false};
|
||||
const StringName k__copy_path{"_copy_path",false};
|
||||
const StringName k__diagnose_no_route_reason{"_diagnose_no_route_reason",false};
|
||||
const StringName k__find_facility_access_path{"_find_facility_access_path",false};
|
||||
const StringName k__find_path_internal{"_find_path_internal",false};
|
||||
const StringName k__guest_access_batch_can_select{"_guest_access_batch_can_select",false};
|
||||
const StringName k__guest_batch_has_permitted_layer_crossing{"_guest_batch_has_permitted_layer_crossing",false};
|
||||
const StringName k__guest_has_checked_in{"_guest_has_checked_in",false};
|
||||
const StringName k__guest_step_connector_memo{"_guest_step_connector_memo",false};
|
||||
const StringName k__guest_tree_arithmetic_wrapped{"_guest_tree_arithmetic_wrapped",false};
|
||||
const StringName k__guest_tree_reconstruct_path{"_guest_tree_reconstruct_path",false};
|
||||
const StringName k__need_relief_for_facility_tags{"_need_relief_for_facility_tags",false};
|
||||
const StringName k__passable_cells{"_passable_cells",false};
|
||||
const StringName k__path_cache{"_path_cache",false};
|
||||
const StringName k__query_batch{"_query_batch",false};
|
||||
const StringName k__route_failure{"_route_failure",false};
|
||||
const StringName k__service_category_for_tags{"_service_category_for_tags",false};
|
||||
const StringName k__sorted_name_keys{"_sorted_name_keys",false};
|
||||
const StringName k_arithmetic_wrapped{"arithmetic_wrapped",false};
|
||||
const StringName k_available_quantity{"available_quantity",false};
|
||||
const StringName k_base_quality{"base_quality",false};
|
||||
const StringName k_base_service_minutes{"base_service_minutes",false};
|
||||
const StringName k_begin_guest_path_query_batch{"begin_guest_path_query_batch",false};
|
||||
const StringName k_cache_misses{"cache_misses",false};
|
||||
const StringName k_calculate_guest_batch_path_cost{"calculate_guest_batch_path_cost",false};
|
||||
const StringName k_candidates{"candidates",false};
|
||||
const StringName k_capacity{"capacity",false};
|
||||
const StringName k_compact{"compact",false};
|
||||
const StringName k_cost{"cost",false};
|
||||
const StringName k_cost_values{"cost_values",false};
|
||||
const StringName k_costs{"costs",false};
|
||||
const StringName k_current_cell{"current_cell",false};
|
||||
const StringName k_definition_id{"definition_id",false};
|
||||
const StringName k_definition_registry{"definition_registry",false};
|
||||
const StringName k_elevators{"elevators",false};
|
||||
const StringName k_end_guest_path_query_batch{"end_guest_path_query_batch",false};
|
||||
const StringName k_entrance{"entrance",false};
|
||||
const StringName k_estimated_total_gold{"estimated_total_gold",false};
|
||||
const StringName k_expected_quality{"expected_quality",false};
|
||||
const StringName k_expected_wait{"expected_wait",false};
|
||||
const StringName k_facility_id{"facility_id",false};
|
||||
const StringName k_facility_service{"facility_service",false};
|
||||
const StringName k_facility_tags{"facility_tags",false};
|
||||
const StringName k_get_connector{"get_connector",false};
|
||||
const StringName k_get_facility{"get_facility",false};
|
||||
const StringName k_get_facility_business_price{"get_facility_business_price",false};
|
||||
const StringName k_get_guest{"get_guest",false};
|
||||
const StringName k_get_instances{"get_instances",false};
|
||||
const StringName k_get_step_connector{"get_step_connector",false};
|
||||
const StringName k_graph_revision{"graph_revision",false};
|
||||
const StringName k_group_fit{"group_fit",false};
|
||||
const StringName k_guest_service{"guest_service",false};
|
||||
const StringName k_illegal_level{"illegal_level",false};
|
||||
const StringName k_indices{"indices",false};
|
||||
const StringName k_inputs_available{"inputs_available",false};
|
||||
const StringName k_instance_id{"instance_id",false};
|
||||
const StringName k_inventory_inputs{"inventory_inputs",false};
|
||||
const StringName k_inventory_service{"inventory_service",false};
|
||||
const StringName k_is_open{"is_open",false};
|
||||
const StringName k_last_error{"last_error",false};
|
||||
const StringName k_last_no_route_reason{"last_no_route_reason",false};
|
||||
const StringName k_last_path_cost{"last_path_cost",false};
|
||||
const StringName k_local_inventory_id{"local_inventory_id",false};
|
||||
const StringName k_movement_type{"movement_type",false};
|
||||
const StringName k_need_relief{"need_relief",false};
|
||||
const StringName k_occupied_cells{"occupied_cells",false};
|
||||
const StringName k_origin{"origin",false};
|
||||
const StringName k_owns_connector{"owns_connector",false};
|
||||
const StringName k_path{"path",false};
|
||||
const StringName k_path_handle{"path_handle",false};
|
||||
const StringName k_path_query_measured{"path_query_measured",false};
|
||||
const StringName k_path_service{"path_service",false};
|
||||
const StringName k_queue_entries{"queue_entries",false};
|
||||
const StringName k_reachable{"reachable",false};
|
||||
const StringName k_ready{"ready",false};
|
||||
const StringName k_resource_path{"resource_path",false};
|
||||
const StringName k_risk_level{"risk_level",false};
|
||||
const StringName k_service_category{"service_category",false};
|
||||
const StringName k_settled{"settled",false};
|
||||
const StringName k_staff_only{"staff_only",false};
|
||||
const StringName k_start{"start",false};
|
||||
const StringName k_state{"state",false};
|
||||
const StringName k_supported{"supported",false};
|
||||
const StringName k_tags{"tags",false};
|
||||
const StringName k_tagproj_hunger{"hunger",false};
|
||||
const StringName k_tagproj_sleep{"sleep",false};
|
||||
const StringName k_tagproj_hygiene{"hygiene",false};
|
||||
const StringName k_tagproj_medical{"medical",false};
|
||||
const StringName k_tagproj_leisure{"leisure",false};
|
||||
const StringName k_tagproj_intimacy{"intimacy",false};
|
||||
const StringName k_tagproj_dining{"dining",false};
|
||||
const StringName k_tagproj_lodging{"lodging",false};
|
||||
const StringName k_tagproj_social{"social",false};
|
||||
const StringName k_tagproj_bath{"bath",false};
|
||||
const StringName k_tagproj_wellness{"wellness",false};
|
||||
const StringName k_tagproj_adult_only{"adult_only",false};
|
||||
const StringName k_tagproj_restricted_service{"restricted_service",false};
|
||||
const StringName k_tagproj_food{"food",false};
|
||||
const StringName k_tagproj_service{"service",false};
|
||||
const StringName k_target_cell{"target_cell",false};
|
||||
const StringName k_targets{"targets",false};
|
||||
const StringName k_temporary_block{"temporary_block",false};
|
||||
const StringName k_travel_minutes{"travel_minutes",false};
|
||||
const StringName k_tree{"tree",false};
|
||||
const StringName k_usable{"usable",false};
|
||||
};
|
||||
public:
|
||||
// Whole builtin integer record operation. No retained sample values or time
|
||||
// suppression. Unsupported data returns before any caller-owned mutation.
|
||||
Dictionary performance_record_sample(Object *owner, const StringName &metric, int64_t duration) {
|
||||
const StringName supported("supported",false), report_key("report",false);
|
||||
Dictionary unsupported; unsupported[supported]=false;
|
||||
Object *script=owner ? owner->get_script() : nullptr;
|
||||
if (!script || String(script->get(StringName("resource_path",false))) != "res://scripts/core/performance_budget.gd" || metric==StringName() || duration<0) return unsupported;
|
||||
const StringName samples_key("_samples",false), ordered_key("_ordered_samples",false), bytes_key("_ordered_sample_bytes",false), integer_key("_integer_window_bytes",false), totals_key("_totals",false), counts_key("_sample_counts",false), budgets_key("_budgets",false);
|
||||
const Variant sv=owner->get(samples_key), ov=owner->get(ordered_key), bv=owner->get(bytes_key), iv=owner->get(integer_key), tv=owner->get(totals_key), cv=owner->get(counts_key), uv=owner->get(budgets_key), capacityv=owner->get(StringName("sample_capacity",false));
|
||||
if (sv.get_type()!=Variant::DICTIONARY || ov.get_type()!=Variant::DICTIONARY || bv.get_type()!=Variant::DICTIONARY || iv.get_type()!=Variant::DICTIONARY || tv.get_type()!=Variant::DICTIONARY || cv.get_type()!=Variant::DICTIONARY || uv.get_type()!=Variant::DICTIONARY || capacityv.get_type()!=Variant::INT) return unsupported;
|
||||
Dictionary samples=sv, orders=ov, rawbytes=bv, integers=iv, totals=tv, counts=cv, budgets=uv;
|
||||
if (samples.is_read_only() || orders.is_read_only() || rawbytes.is_read_only() || integers.is_read_only() || totals.is_read_only() || counts.is_read_only()) return unsupported;
|
||||
const std::vector<Dictionary> maps{samples,orders,rawbytes,integers,totals,counts,budgets};
|
||||
for (size_t i=0;i<maps.size();++i) for (size_t j=i+1;j<maps.size();++j)
|
||||
if (UtilityFunctions::is_same(maps[i],maps[j])) return unsupported;
|
||||
const bool existing=samples.has(metric);
|
||||
Variant rawv=samples.get(metric,Array());
|
||||
if (rawv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array values=rawv;
|
||||
if (values.is_read_only()) return unsupported;
|
||||
for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;
|
||||
Variant totalv=totals.get(metric,0), countv=counts.get(metric,0), budgetv=budgets.get(metric,Dictionary());
|
||||
if (totalv.get_type()!=Variant::INT || countv.get_type()!=Variant::INT || budgetv.get_type()!=Variant::DICTIONARY) return unsupported;
|
||||
Dictionary budget=budgetv;
|
||||
for (const Dictionary &map: maps) if (UtilityFunctions::is_same(budget,map)) return unsupported;
|
||||
const StringName aggregation_key("aggregation",false), limit_key("limit_us",false);
|
||||
Variant aggregationv=budget.get(aggregation_key,1), limitv=budget.get(limit_key,0);
|
||||
if (aggregationv.get_type()!=Variant::INT || limitv.get_type()!=Variant::INT) return unsupported;
|
||||
// One exact byte snapshot serves both builtin validation helpers inside
|
||||
// this call; there are no external calls between their original checks.
|
||||
PackedByteArray before=UtilityFunctions::var_to_bytes(values);
|
||||
TypedArray<int64_t> ordered;
|
||||
Variant savedbytes=rawbytes.get(metric,PackedByteArray());
|
||||
bool reuse=existing && orders.has(metric) && savedbytes.get_type()==Variant::PACKED_BYTE_ARRAY && PackedByteArray(savedbytes)==before;
|
||||
if (reuse) {
|
||||
Variant orderv=orders[metric];
|
||||
if (orderv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array prior=orderv;
|
||||
if (prior.is_read_only() || !prior.is_typed() || prior.get_typed_builtin()!=Variant::INT || prior.size()!=values.size()) return unsupported;
|
||||
ordered=prior;
|
||||
} else {
|
||||
for (int64_t i=0;i<values.size();++i) ordered.append(values[i]);
|
||||
ordered.sort();
|
||||
}
|
||||
// Guards completed: mutations preserve raw and reused ordered aliases.
|
||||
if (!existing) samples[metric]=values;
|
||||
integers[metric]=before;
|
||||
if (!reuse) { orders[metric]=ordered; rawbytes[metric]=before; }
|
||||
values.append(duration);
|
||||
ordered.insert(ordered.bsearch(duration),duration);
|
||||
if (values.size()>int64_t(capacityv)) {
|
||||
Variant removed=values.pop_front();
|
||||
ordered.remove_at(ordered.bsearch(removed));
|
||||
}
|
||||
PackedByteArray after=UtilityFunctions::var_to_bytes(values);
|
||||
rawbytes[metric]=after; integers[metric]=after;
|
||||
const int64_t lifetime_total=the_tree_native::signed_add(int64_t(totalv),duration);
|
||||
const int64_t lifetime_count=the_tree_native::signed_add(int64_t(countv),1);
|
||||
totals[metric]=lifetime_total; counts[metric]=lifetime_count;
|
||||
const int64_t average=lifetime_count>0 ? lifetime_total/lifetime_count : 0;
|
||||
const int64_t maximum=ordered.is_empty() ? 0 : std::max<int64_t>(0,int64_t(ordered[ordered.size()-1]));
|
||||
int64_t p95=0;
|
||||
if (!ordered.is_empty()) {
|
||||
const int64_t index=std::max<int64_t>(the_tree_native::signed_add(the_tree_native::signed_add(signed_product(95,ordered.size()),99)/100,-1),0);
|
||||
p95=ordered[std::min<int64_t>(index,ordered.size()-1)];
|
||||
}
|
||||
const int64_t aggregation=aggregationv, limit=limitv;
|
||||
// Original default match is P95, including unknown aggregation values.
|
||||
const int64_t measured=aggregation==0 ? average : (aggregation==2 ? maximum : p95);
|
||||
Dictionary report;
|
||||
report[StringName("metric_id",false)]=metric;
|
||||
report[StringName("window_sample_count",false)]=values.size();
|
||||
report[StringName("lifetime_sample_count",false)]=lifetime_count;
|
||||
report[StringName("average_us",false)]=average;
|
||||
report[StringName("p95_us",false)]=p95;
|
||||
report[StringName("maximum_us",false)]=maximum;
|
||||
report[aggregation_key]=aggregation;
|
||||
report[StringName("measured_us",false)]=measured;
|
||||
report[limit_key]=limit;
|
||||
report[StringName("budget_exceeded",false)]=limit>0 && measured>limit;
|
||||
Dictionary result; result[supported]=true; result[report_key]=report; return result;
|
||||
}
|
||||
void clear_edge_cache() { cached_edges.clear(); }
|
||||
Dictionary search(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, false);
|
||||
}
|
||||
Dictionary search_cached(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, true);
|
||||
}
|
||||
// One ordered projection, with no new persistent ownership/cache. Only
|
||||
// builtin pure getters and the already guarded sole budget observer qualify.
|
||||
Dictionary project_guest_candidates(Object *runtime, const StringName &guest_id,
|
||||
int64_t open_state, int64_t degraded_state, int64_t movement_scale,
|
||||
int64_t stair_credit, int64_t orthogonal_cost, const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!builtin(n,runtime, "res://autoload/simulation_runtime.gd") || !ticks.is_valid()) return unsupported;
|
||||
Object *path = runtime->get(n.k_path_service);
|
||||
Object *elevators = runtime->get(n.k_elevators);
|
||||
Object *facilities = runtime->get(n.k_facility_service);
|
||||
Object *definitions = runtime->get(n.k_definition_registry);
|
||||
Object *inventory = runtime->get(n.k_inventory_service);
|
||||
Object *guests = runtime->get(n.k_guest_service);
|
||||
if (!builtin(n,path,"res://scripts/world/path_service.gd") ||
|
||||
!builtin(n,elevators,"res://scripts/transport/elevator_service.gd") ||
|
||||
!builtin(n,facilities,"res://scripts/facility/facility_service.gd") ||
|
||||
!builtin(n,definitions,"res://scripts/core/definition_registry.gd") ||
|
||||
!builtin(n,inventory,"res://scripts/inventory/inventory_service.gd") ||
|
||||
!builtin(n,guests,"res://scripts/guest/guest_service.gd") ||
|
||||
!static_cast<bool>(runtime->call(n.k__guest_access_batch_can_select))) return unsupported;
|
||||
Object *guest = guests->call(n.k_get_guest,guest_id);
|
||||
Array candidates;
|
||||
if (!guest) {
|
||||
Dictionary result; result[n.k_supported] = true;
|
||||
result[n.k_candidates] = candidates; return result;
|
||||
}
|
||||
if (!builtin(n,guest,"res://scripts/guest/guest_runtime.gd")) return unsupported;
|
||||
const Array initial = facilities->call(n.k_get_instances);
|
||||
// Preflight is read-only: default registry/get_instances have no public
|
||||
// error/counter effects. Custom resources retain the original whole loop.
|
||||
for (int64_t i=0;i<initial.size();++i) {
|
||||
Object *facility = initial[i];
|
||||
if (!builtin(n,facility,"res://scripts/facility/facility_instance.gd")) return unsupported;
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (definition && !builtin(n,definition,"res://scripts/data/facility_definition.gd")) return unsupported;
|
||||
}
|
||||
Array goals;
|
||||
for (int64_t i=0;i<initial.size();++i) goals.append_array(facility_access_cells(n,path,initial[i]));
|
||||
const Vector3i start = guest->get(n.k_current_cell);
|
||||
path->call(n.k_begin_guest_path_query_batch,start,goals);
|
||||
const Array ordered = facilities->call(n.k_get_instances);
|
||||
TypedArray<int64_t> layers; layers.push_back(0); layers.push_back(1);
|
||||
TypedArray<StringName> access_ids;
|
||||
for (int64_t i=0;i<ordered.size();++i) {
|
||||
Object *facility = ordered[i];
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (!definition) continue;
|
||||
const Array tags = definition->get(n.k_tags);
|
||||
if (tags.has(n.k_staff_only)) continue;
|
||||
if (tags.has(n.k_entrance) && static_cast<bool>(runtime->call(n.k__guest_has_checked_in,guest))) continue;
|
||||
bool has_inputs = true;
|
||||
const Dictionary inputs = definition->get(n.k_inventory_inputs);
|
||||
const Array items = runtime->call(n.k__sorted_name_keys,inputs);
|
||||
for (int64_t j=0;j<items.size();++j) {
|
||||
const int64_t quantity = inventory->call(n.k_available_quantity,facility->get(n.k_local_inventory_id),items[j]);
|
||||
if (quantity < static_cast<int64_t>(inputs[items[j]])) {has_inputs=false;break;}
|
||||
}
|
||||
if (!has_inputs) {
|
||||
Dictionary row;row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_inputs_available]=false;candidates.push_back(row);continue;
|
||||
}
|
||||
const Array destinations = facility_access_cells(n,path,facility);
|
||||
Dictionary route = nearest_guest_access_impl(n,path,start,destinations,facility->get(n.k_origin),layers,access_ids,ticks);
|
||||
if (static_cast<bool>(route.get(n.k_supported,false))) route.erase(n.k_supported);
|
||||
else route = Dictionary(runtime->call(n.k__find_facility_access_path,start,facility));
|
||||
const Array walk = route.get(n.k_path,Array());
|
||||
const bool plain_tag_relief = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
const Dictionary relief = plain_tag_relief ? builtin_tag_relief(n,tags) :
|
||||
Dictionary(runtime->call(n.k__need_relief_for_facility_tags,tags));
|
||||
Dictionary row;
|
||||
row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_facility_tags]=tags.duplicate();
|
||||
const int64_t state = facility->get(n.k_state);
|
||||
row[n.k_is_open]=(state==open_state || state==degraded_state);
|
||||
row[n.k_reachable]=route.get(n.k_reachable,false);
|
||||
row[n.k_travel_minutes]=guest_path_minutes(n,path,elevators,walk,movement_scale,stair_credit,orthogonal_cost);
|
||||
const Array queue = facility->get(n.k_queue_entries);
|
||||
const int64_t capacity = facility->get(n.k_capacity);
|
||||
row[n.k_expected_wait]=signed_product(queue.size(),facility->get(n.k_base_service_minutes))/std::max<int64_t>(capacity,1);
|
||||
row[n.k_estimated_total_gold]=runtime->call(n.k_get_facility_business_price,facility);
|
||||
const int64_t quality = facility->get(n.k_base_quality);
|
||||
row[n.k_expected_quality]=std::max<int64_t>(0,std::min<int64_t>(quality,100));
|
||||
row[n.k_need_relief]=relief;
|
||||
row[n.k_risk_level]=int64_t(0);row[n.k_illegal_level]=int64_t(0);
|
||||
row[n.k_group_fit]=signed_product(std::min<int64_t>(capacity,4),5);
|
||||
// Recheck at the original call position: preceding price/observer
|
||||
// callbacks must retain a replaced/custom runtime's original call.
|
||||
const bool plain_tag_category = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
row[n.k_service_category]=plain_tag_category ? Variant(builtin_tag_category(n,tags)) :
|
||||
runtime->call(n.k__service_category_for_tags,tags);
|
||||
Dictionary handle;handle[n.k_target_cell]=route.get(n.k_target_cell,guest->get(n.k_current_cell));
|
||||
handle[n.k_graph_revision]=path->get(n.k_graph_revision);
|
||||
row[n.k_path_handle]=handle;candidates.push_back(row);
|
||||
}
|
||||
path->call(n.k_end_guest_path_query_batch);
|
||||
Dictionary result;result[n.k_supported]=true;result[n.k_candidates]=candidates;return result;
|
||||
}
|
||||
private:
|
||||
// Direct translation of the two pure builtin tag methods. Fresh result,
|
||||
// original insertion/branch order, no retained definition/tag memo.
|
||||
static Dictionary builtin_tag_relief(const ProjectionNames &n,const Array &tags) {
|
||||
Dictionary relief;
|
||||
relief[n.k_tagproj_hunger]=int64_t(0);relief[n.k_tagproj_sleep]=int64_t(0);
|
||||
relief[n.k_tagproj_hygiene]=int64_t(0);relief[n.k_tagproj_medical]=int64_t(0);
|
||||
relief[n.k_tagproj_leisure]=int64_t(0);relief[n.k_tagproj_intimacy]=int64_t(0);
|
||||
if(tags.has(n.k_tagproj_dining)) {relief[n.k_tagproj_hunger]=int64_t(80);relief[n.k_tagproj_leisure]=int64_t(10);}
|
||||
if(tags.has(n.k_tagproj_lodging)) {relief[n.k_tagproj_sleep]=int64_t(90);relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),10);}
|
||||
if(tags.has(n.k_tagproj_social)||tags.has(n.k_tagproj_leisure)) relief[n.k_tagproj_leisure]=std::max<int64_t>(int64_t(relief[n.k_tagproj_leisure]),40);
|
||||
if(tags.has(n.k_tagproj_hygiene)||tags.has(n.k_tagproj_bath)) relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),80);
|
||||
if(tags.has(n.k_tagproj_medical)) relief[n.k_tagproj_medical]=int64_t(90);
|
||||
else if(tags.has(n.k_tagproj_wellness)) relief[n.k_tagproj_medical]=std::max<int64_t>(int64_t(relief[n.k_tagproj_medical]),20);
|
||||
if(tags.has(n.k_tagproj_adult_only)||tags.has(n.k_tagproj_restricted_service)) relief[n.k_tagproj_intimacy]=int64_t(80);
|
||||
return relief;
|
||||
}
|
||||
static StringName builtin_tag_category(const ProjectionNames &n,const Array &tags) {
|
||||
if(tags.has(n.k_tagproj_dining))return n.k_tagproj_food;
|
||||
if(tags.has(n.k_tagproj_lodging))return n.k_tagproj_lodging;
|
||||
if(tags.has(n.k_tagproj_social))return n.k_tagproj_leisure;
|
||||
return n.k_tagproj_service;
|
||||
}
|
||||
static bool builtin(const ProjectionNames &n,Object *object,const char *resource) {
|
||||
if (!object) return false;Object *script=object->get_script();
|
||||
return script && String(script->get(n.k_resource_path))==resource;
|
||||
}
|
||||
// Exact builtin cache-key spelling. No normalized/reordered profile and no
|
||||
// persistent cache; String lengths count Unicode codepoints as GDScript does.
|
||||
static String cache_key_suffix(const ProjectionNames &n,Object *path, const TypedArray<int64_t> &layers,
|
||||
const TypedArray<StringName> &access) {
|
||||
String layer_text, access_text;
|
||||
for (int64_t i=0; i<layers.size(); ++i) {
|
||||
if (i) layer_text += ",";
|
||||
layer_text += String::num_int64(static_cast<int64_t>(layers[i]));
|
||||
}
|
||||
for (int64_t i=0; i<access.size(); ++i) {
|
||||
if (i) access_text += ",";
|
||||
const String value = String(static_cast<StringName>(access[i]));
|
||||
access_text += String::num_int64(value.length()) + ":" + value;
|
||||
}
|
||||
return String("|L:") + layer_text + "|A:" + access_text + "|R:" +
|
||||
String::num_int64(static_cast<int64_t>(path->get(n.k_graph_revision)));
|
||||
}
|
||||
static String cache_key_with_suffix(const Vector3i &start, const Vector3i &goal,
|
||||
const String &suffix) {
|
||||
return String::num_int64(start.x) + "," + String::num_int64(start.y) + "," +
|
||||
String::num_int64(start.z) + ">" + String::num_int64(goal.x) + "," +
|
||||
String::num_int64(goal.y) + "," + String::num_int64(goal.z) + suffix;
|
||||
}
|
||||
static int64_t signed_product(int64_t a,int64_t b) {
|
||||
const uint64_t product=static_cast<uint64_t>(a)*static_cast<uint64_t>(b);
|
||||
if (product<=static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) return static_cast<int64_t>(product);
|
||||
return std::numeric_limits<int64_t>::min()+static_cast<int64_t>(product-(uint64_t(1)<<63));
|
||||
}
|
||||
static Array facility_access_cells(const ProjectionNames &n,Object *path,Object *facility) {
|
||||
const Dictionary passable=path->get(n.k__passable_cells);
|
||||
Array occupied=Array(facility->get(n.k_occupied_cells)).duplicate();
|
||||
if (occupied.is_empty()) occupied.push_back(facility->get(n.k_origin));
|
||||
Dictionary unique;
|
||||
for (int64_t i=0;i<occupied.size();++i) {
|
||||
const Vector3i cell=occupied[i];
|
||||
for (int32_t dx : {-1,1}) {
|
||||
// Match GDScript's Vector3i conversion after int64 addition.
|
||||
const Vector3i candidate(int64_t(cell.x)+dx,cell.y,cell.z);
|
||||
if (passable.has(candidate)) unique[candidate]=true;
|
||||
}
|
||||
}
|
||||
const Vector3i origin=facility->get(n.k_origin);if(passable.has(origin)) unique[origin]=true;
|
||||
const Array keys=unique.keys();std::vector<Vector3i> sorted;sorted.reserve(keys.size());
|
||||
for (int64_t i=0;i<keys.size();++i) sorted.push_back(keys[i]);
|
||||
std::sort(sorted.begin(),sorted.end(),[](const Vector3i &a,const Vector3i &b){
|
||||
if(a.z!=b.z)return a.z<b.z;if(a.y!=b.y)return a.y<b.y;return a.x<b.x;
|
||||
});
|
||||
Array result;for(const Vector3i &cell:sorted)result.push_back(cell);return result;
|
||||
}
|
||||
static int64_t guest_path_minutes(const ProjectionNames &n,Object *path,Object *elevators,const Array &route,
|
||||
int64_t movement_scale,int64_t stair_credit,int64_t orthogonal_cost) {
|
||||
Dictionary memo=path->get(n.k__guest_step_connector_memo);
|
||||
// Route-local only. Reuse an ownership result only after an actual
|
||||
// BOOL return and a typed cached step; no script callback occurs on a
|
||||
// repeated typed memo hit. A miss/conversion/custom script keeps calls.
|
||||
const bool builtin_pair = builtin(n,path,"res://scripts/world/path_service.gd") &&
|
||||
builtin(n,elevators,"res://scripts/transport/elevator_service.gd");
|
||||
bool ownership_valid = false;
|
||||
bool ownership_value = false;
|
||||
StringName ownership_connector;
|
||||
for(int64_t i=1;i<route.size();++i) {
|
||||
const Vector3i from=route[i-1],to=route[i];
|
||||
const Dictionary destinations=memo.get(from,Dictionary());
|
||||
StringName connector;
|
||||
const bool memo_hit = destinations.has(to);
|
||||
const bool typed_memo_hit = memo_hit && destinations[to].get_type() == Variant::STRING_NAME;
|
||||
if(memo_hit)connector=destinations[to];
|
||||
else {TypedArray<StringName> access;connector=path->call(n.k_get_step_connector,from,to,access);}
|
||||
bool special;
|
||||
if (builtin_pair && typed_memo_hit && ownership_valid && connector == ownership_connector) {
|
||||
special = ownership_value;
|
||||
} else {
|
||||
const Variant owned = elevators->call(n.k_owns_connector,connector);
|
||||
special = static_cast<bool>(owned);
|
||||
ownership_valid = builtin_pair && typed_memo_hit && owned.get_type() == Variant::BOOL;
|
||||
ownership_value = special;
|
||||
ownership_connector = connector;
|
||||
}
|
||||
if(!special && String(connector).begins_with("infra.connector:") && from.x==to.x && from.z==to.z &&
|
||||
std::abs(int64_t(from.y)-int64_t(to.y))==1) {
|
||||
const Dictionary detail=path->call(n.k_get_connector,connector);
|
||||
special=detail.get(n.k_movement_type,StringName())==Variant(n.k_CONNECTOR) &&
|
||||
static_cast<int64_t>(detail.get(n.k_cost,int64_t(0)))==150 && stair_credit>movement_scale;
|
||||
}
|
||||
if(special) {
|
||||
const int64_t cost=path->call(n.k_calculate_guest_batch_path_cost,route[0],route[route.size()-1],route);
|
||||
return static_cast<int64_t>(std::ceil(static_cast<double>(cost)/static_cast<double>(orthogonal_cost)));
|
||||
}
|
||||
}
|
||||
return std::max<int64_t>(route.size()-1,0);
|
||||
}
|
||||
public:
|
||||
|
||||
// Stateless translation of the builtin nearest access query. The caller
|
||||
// supplies the existing typed default profile and the real Time callable.
|
||||
// Public cache publication, failure diagnostics and fallback stay ordered.
|
||||
Dictionary nearest_guest_access(Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
return nearest_guest_access_impl(n,path,start,goals,fallback_target,layers,access,ticks);
|
||||
}
|
||||
private:
|
||||
Dictionary nearest_guest_access_impl(const ProjectionNames &n,Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!path || !ticks.is_valid() || goals.is_empty()) return unsupported;
|
||||
Object *script = path->get_script();
|
||||
if (!script || String(script->get(n.k_resource_path)) != "res://scripts/world/path_service.gd") return unsupported;
|
||||
Dictionary batch = path->get(n.k__query_batch);
|
||||
if (batch.is_empty() || start != static_cast<Vector3i>(batch[n.k_start])) return unsupported;
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start) || blocked(n,path, start) || !layers.has(start.z)) return unsupported;
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
// Exact builtin validation loop has no callback before ticks.call().
|
||||
// Build the unchanged profile/revision suffix lazily at the first key.
|
||||
String key_suffix;
|
||||
bool key_suffix_ready = false;
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
if (goals[j].get_type() != Variant::VECTOR3I) return unsupported;
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell)) {
|
||||
const bool searchable = passable.has(cell) && layers.has(cell.z) && !blocked(n,path, cell);
|
||||
if (searchable && (start.z == cell.z || static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, cell, layers, access)))) return unsupported;
|
||||
}
|
||||
if (!key_suffix_ready) {
|
||||
key_suffix = cache_key_suffix(n,path, layers, access);
|
||||
key_suffix_ready = true;
|
||||
}
|
||||
const String key = cache_key_with_suffix(start, cell, key_suffix);
|
||||
if (cache.has(key)) return unsupported;
|
||||
}
|
||||
const int64_t started = ticks.call();
|
||||
if (!static_cast<bool>(batch[n.k_ready])) {
|
||||
batch[n.k_tree] = path->call(n.k__build_guest_query_tree_compact, start, targets, layers, access);
|
||||
batch[n.k_arithmetic_wrapped] = path->get(n.k__guest_tree_arithmetic_wrapped);
|
||||
batch[n.k_ready] = true;
|
||||
}
|
||||
const Dictionary tree = batch[n.k_tree];
|
||||
if (!static_cast<bool>(tree.get(n.k_usable, false)) || static_cast<bool>(batch.get(n.k_arithmetic_wrapped, false))) return unsupported;
|
||||
Vector3i best_cell = fallback_target;
|
||||
bool found = false;
|
||||
int64_t best_cost = 0;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell) || !settled.has(cell)) continue;
|
||||
if (!tree_has_cost(n,tree, cell)) return unsupported;
|
||||
const int64_t cost = tree_cost(n,tree, cell);
|
||||
if (!found || cost < best_cost) {
|
||||
found = true;
|
||||
best_cost = cost;
|
||||
best_cell = cell;
|
||||
}
|
||||
}
|
||||
TypedArray<Vector3i> best_path;
|
||||
if (found) {
|
||||
best_path = resolve_ready_guest_point(n,path, start, best_cell, tree, layers, access);
|
||||
if (best_path.is_empty()) return unsupported;
|
||||
}
|
||||
if (!goals.is_empty() && (!found || goals[goals.size() - 1] != Variant(best_cell))) {
|
||||
resolve_ready_guest_point(n,path, start, goals[goals.size() - 1], tree, layers, access);
|
||||
}
|
||||
const int64_t elapsed = the_tree_native::signed_add(static_cast<int64_t>(ticks.call()), the_tree_native::signed_add(~started, 1));
|
||||
path->emit_signal(n.k_path_query_measured, std::max<int64_t>(elapsed, 0));
|
||||
Dictionary result;
|
||||
result[n.k_supported] = true;
|
||||
result[n.k_reachable] = found;
|
||||
result[n.k_path] = best_path;
|
||||
result[n.k_target_cell] = best_cell;
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
static bool blocked(const ProjectionNames &n,Object *path, const Vector3i &cell) {
|
||||
const Dictionary traffic_map = path->get(n.k__cell_traffic);
|
||||
const Dictionary traffic = traffic_map.get(cell, Dictionary());
|
||||
return !traffic.is_empty() && static_cast<bool>(traffic[n.k_temporary_block]);
|
||||
}
|
||||
static bool tree_has_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return values.has(cell);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
if (!indices.has(cell)) return false;
|
||||
const int64_t index = indices[cell];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return index >= 0 && index < values.size();
|
||||
}
|
||||
static int64_t tree_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return static_cast<int64_t>(values[cell]);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return values[static_cast<int64_t>(indices[cell])];
|
||||
}
|
||||
static TypedArray<Vector3i> resolve_ready_guest_point(const ProjectionNames &n,Object *path,
|
||||
const Vector3i &start, const Vector3i &goal, const Dictionary &tree,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access) {
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start)) return path->call(n.k__route_failure, n.k_START_NOT_PASSABLE);
|
||||
if (!passable.has(goal)) return path->call(n.k__route_failure, n.k_GOAL_NOT_PASSABLE);
|
||||
if (!layers.has(start.z) || !layers.has(goal.z)) return path->call(n.k__route_failure, n.k_ACCESS_LAYER_DENIED);
|
||||
if (blocked(n,path, start)) return path->call(n.k__route_failure, n.k_START_TEMPORARILY_BLOCKED);
|
||||
if (blocked(n,path, goal)) return path->call(n.k__route_failure, n.k_GOAL_TEMPORARILY_BLOCKED);
|
||||
// ticks/tree callbacks may have changed the script or revision. Recheck
|
||||
// at this original call position and never reuse the pre-tick suffix.
|
||||
const String key = builtin(n,path,"res://scripts/world/path_service.gd") ?
|
||||
cache_key_with_suffix(start, goal, cache_key_suffix(n,path, layers, access)) :
|
||||
String(path->call(n.k__cache_key, start, goal, layers, access));
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
const Dictionary batch = path->get(n.k__query_batch);
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
if (cache.has(key) || !targets.has(goal)) return path->call(n.k__find_path_internal, start, goal);
|
||||
path->set(n.k_cache_misses, the_tree_native::signed_add(static_cast<int64_t>(path->get(n.k_cache_misses)), 1));
|
||||
if (start.z != goal.z && !static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, goal, layers, access))) return path->call(n.k__route_failure, n.k_GOAL_UNREACHABLE);
|
||||
TypedArray<Vector3i> route;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
if (start == goal) route.push_back(start);
|
||||
else if (settled.has(goal)) route = Array(path->call(n.k__guest_tree_reconstruct_path, tree, goal));
|
||||
if (route.is_empty()) return path->call(n.k__route_failure, path->call(n.k__diagnose_no_route_reason, start, goal, layers, access));
|
||||
cache[key] = path->call(n.k__copy_path, route);
|
||||
path->set(n.k_last_no_route_reason, n.k_OK);
|
||||
path->set(n.k_last_path_cost, tree_cost(n,tree, goal));
|
||||
path->set(n.k_last_error, String(""));
|
||||
return route;
|
||||
}
|
||||
public:
|
||||
private:
|
||||
// One existing builtin provider operation, with no new value cache. Only
|
||||
// connector-free WALK rows qualify; unsupported cases call the old provider.
|
||||
// All work stays private until the original final shared[current] assignment.
|
||||
struct WalkEdgeNames {
|
||||
const StringName resource_path{"resource_path",false}, constants_method{"get_script_constant_map",false};
|
||||
const StringName front{"FRONT",false}, back{"BACK",false}, walk{"MOVEMENT_WALK",false};
|
||||
const StringName base{"ORTHOGONAL_MOVE_COST",false}, congestion{"CONGESTION_COST_PER_EXCESS",false};
|
||||
const StringName workspace{"_guest_tree_workspace",false}, edges{"edges",false}, indices{"indices",false};
|
||||
const StringName adjacency{"_adjacency",false}, traffic{"_cell_traffic",false}, vertical{"_vertical_walk_requires_connector",false};
|
||||
const StringName to{"to",false}, connector{"connector_id",false}, access{"access_id",false}, movement{"movement_type",false};
|
||||
const StringName base_cost{"base_cost",false}, cost{"cost",false}, temporary{"temporary_block",false};
|
||||
const StringName dynamic{"dynamic_cost",false}, density{"current_density",false}, capacity{"base_capacity",false};
|
||||
const StringName provider{"_guest_tree_shared_edges",false};
|
||||
};
|
||||
struct WalkEdgeContext {
|
||||
Object *path=nullptr;
|
||||
int64_t front=0, back=0, base=0, congestion=0;
|
||||
StringName walk;
|
||||
};
|
||||
static bool walk_edge_context(const WalkEdgeNames &n,Object *path,const Array &layers,
|
||||
const Array &access,WalkEdgeContext &context) {
|
||||
if (!path || layers.size()!=2 || !access.is_empty() ||
|
||||
layers[0].get_type()!=Variant::INT || layers[1].get_type()!=Variant::INT) return false;
|
||||
Object *script=path->get_script();
|
||||
if (!script || String(script->get(n.resource_path))!="res://scripts/world/path_service.gd" ||
|
||||
!script->has_method(n.constants_method)) return false;
|
||||
const Variant value=script->call(n.constants_method);
|
||||
if (value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary constants=value;
|
||||
for (const StringName &key : {n.front,n.back,n.base,n.congestion})
|
||||
if (constants.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
if (constants.get(n.walk,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
int64_t(layers[0])!=int64_t(constants[n.front]) ||
|
||||
int64_t(layers[1])!=int64_t(constants[n.back])) return false;
|
||||
context.path=path;context.front=constants[n.front];context.back=constants[n.back];
|
||||
context.base=constants[n.base];context.congestion=constants[n.congestion];context.walk=constants[n.walk];
|
||||
return true;
|
||||
}
|
||||
static bool builtin_walk_edge_rows(const WalkEdgeNames &n,const WalkEdgeContext &context,
|
||||
const Vector3i ¤t,Array &rows) {
|
||||
if (!context.path) return false;
|
||||
const Variant workspace_value=context.path->get(n.workspace);
|
||||
if (workspace_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary workspace=workspace_value;
|
||||
const Variant shared_value=workspace.get(n.edges,Variant());
|
||||
if (shared_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
Dictionary shared=shared_value;
|
||||
// Original cached-row branch precedes readonly and all other state reads.
|
||||
if (shared.has(current)) {
|
||||
const Variant cached=shared[current];
|
||||
if (cached.get_type()!=Variant::ARRAY) return false;
|
||||
rows=cached;return true;
|
||||
}
|
||||
if (shared.is_read_only()) return false;
|
||||
const Variant indices_value=workspace.get(n.indices,Variant()), adjacency_value=context.path->get(n.adjacency);
|
||||
const Variant traffic_value=context.path->get(n.traffic), vertical_value=context.path->get(n.vertical);
|
||||
if (indices_value.get_type()!=Variant::DICTIONARY || adjacency_value.get_type()!=Variant::DICTIONARY ||
|
||||
traffic_value.get_type()!=Variant::DICTIONARY || vertical_value.get_type()!=Variant::BOOL) return false;
|
||||
const Dictionary indices=indices_value, adjacency=adjacency_value, traffic=traffic_value;
|
||||
const Variant raw_value=adjacency.get(current,Array());
|
||||
if (raw_value.get_type()!=Variant::ARRAY) return false;
|
||||
const Array raw=raw_value;Array result;
|
||||
for (int64_t i=0;i<raw.size();++i) {
|
||||
if (raw[i].get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary edge=raw[i];
|
||||
if (edge.get(n.to,Variant()).get_type()!=Variant::VECTOR3I) return false;
|
||||
const Vector3i neighbor=edge[n.to];
|
||||
if (neighbor.z!=context.front && neighbor.z!=context.back) continue;
|
||||
const Variant row_value=traffic.get(neighbor,Dictionary());
|
||||
if (row_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary row=row_value;
|
||||
if (!row.is_empty()) {
|
||||
if (row.get(n.temporary,Variant()).get_type()!=Variant::BOOL) return false;
|
||||
if (bool(row[n.temporary])) continue;
|
||||
}
|
||||
// Preserve old method for every connector/access/other movement row.
|
||||
if (edge.get(n.connector,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.access,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.movement,Variant()).get_type()!=Variant::STRING_NAME) return false;
|
||||
const StringName connector=edge[n.connector], access=edge[n.access], movement=edge[n.movement];
|
||||
if (!connector.is_empty() || !access.is_empty() || movement!=context.walk) return false;
|
||||
if (bool(vertical_value) && current.y!=neighbor.y) continue;
|
||||
const Variant base=edge.get(n.base_cost,edge.get(n.cost,context.base));
|
||||
if (base.get_type()!=Variant::INT) return false;
|
||||
int64_t dynamic=0;
|
||||
if (!row.is_empty()) {
|
||||
for (const StringName &key : {n.dynamic,n.density,n.capacity})
|
||||
if (row.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
const int64_t excess=the_tree_native::signed_add(int64_t(row[n.density]),
|
||||
the_tree_native::signed_add(~int64_t(row[n.capacity]),1));
|
||||
dynamic=the_tree_native::signed_add(int64_t(row[n.dynamic]),
|
||||
signed_product(excess>0?excess:0,context.congestion));
|
||||
}
|
||||
const int64_t cost=the_tree_native::signed_add(int64_t(base),dynamic);
|
||||
Array pair;pair.push_back(neighbor);pair.push_back(cost);result.push_back(pair);
|
||||
// Same short circuit and first invalid prefix as the original method.
|
||||
if (cost<=0 || !indices.has(neighbor)) break;
|
||||
}
|
||||
shared[current]=result;rows=result;return true;
|
||||
}
|
||||
public:
|
||||
Dictionary guest_tree_edge_rows(Object *path,const Vector3i ¤t,const Array &layers,const Array &access) {
|
||||
const WalkEdgeNames names;WalkEdgeContext context;Array rows;
|
||||
Dictionary result;const StringName supported{"supported",false}, row_key{"rows",false};
|
||||
result[supported]=false;
|
||||
if (!walk_edge_context(names,path,layers,access,context) ||
|
||||
!builtin_walk_edge_rows(names,context,current,rows)) return result;
|
||||
result[supported]=true;result[row_key]=rows;return result;
|
||||
}
|
||||
private:
|
||||
Dictionary search_impl(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider, bool use_edge_cache) {
|
||||
Dictionary output;
|
||||
output[StringName("status")] = String("unusable");
|
||||
if (start_index < 0 || start_index >= cells.size() || !edge_provider.is_valid()) return output;
|
||||
try {
|
||||
std::vector<I> goal_indices;
|
||||
const Array goal_keys = goals.keys();
|
||||
for (I j = 0; j < goal_keys.size(); ++j)
|
||||
goal_indices.push_back(static_cast<I>(indices.get(goal_keys[j], I(-1))));
|
||||
Array arguments;
|
||||
arguments.resize(1);
|
||||
const WalkEdgeNames walk_names;WalkEdgeContext walk_context;
|
||||
if (edge_provider.get_method()==walk_names.provider && edge_provider.get_argument_count()==1 &&
|
||||
edge_provider.get_bound_arguments_count()==2 && edge_provider.get_unbound_arguments_count()==0) {
|
||||
const Array bound=edge_provider.get_bound_arguments();
|
||||
if (bound.size()==2 && bound[0].get_type()==Variant::ARRAY && bound[1].get_type()==Variant::ARRAY)
|
||||
walk_edge_context(walk_names,edge_provider.get_object(),bound[0],bound[1],walk_context);
|
||||
}
|
||||
// Call the original bound Godot edge method only when the search expands a node.
|
||||
// Preserve its lazy cache population, filter ordering and first invalid row.
|
||||
auto original_edges = [&](I current) {
|
||||
arguments[0] = cells[current];
|
||||
Array fused;bool fused_supported=false;
|
||||
if (cells[current].get_type()==Variant::VECTOR3I) {
|
||||
const Vector3i cell=cells[current];
|
||||
fused_supported=builtin_walk_edge_rows(walk_names,walk_context,cell,fused);
|
||||
}
|
||||
const Variant value = fused_supported ? Variant(fused) : edge_provider.callv(arguments);
|
||||
if (value.get_type() != Variant::ARRAY) return std::vector<Edge>{{-1, 0}};
|
||||
const Array rows = value;
|
||||
std::vector<Edge> result;
|
||||
result.reserve(rows.size());
|
||||
for (I j = 0; j < rows.size(); ++j) {
|
||||
if (rows[j].get_type() != Variant::ARRAY) { result.push_back({-1, 0}); break; }
|
||||
const Array row = rows[j];
|
||||
if (row.size() != 2 || row[1].get_type() != Variant::INT) {
|
||||
result.push_back({-1, 0}); break;
|
||||
}
|
||||
const I cost = static_cast<I>(row[1]);
|
||||
// Nonpositive cost is rejected before requesting the neighbor index.
|
||||
if (cost <= 0) { result.push_back({-1, cost}); break; }
|
||||
const I neighbor = static_cast<I>(indices.get(row[0], I(-1)));
|
||||
result.push_back({neighbor, cost});
|
||||
if (neighbor < 0) break;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto edges = [&](I current) {
|
||||
if (!use_edge_cache) return original_edges(current);
|
||||
const auto found = cached_edges.find(current);
|
||||
if (found != cached_edges.end()) return found->second;
|
||||
// Populate only when the old algorithm expands this same node.
|
||||
// Retain original provider validation and first-invalid prefix.
|
||||
auto converted = original_edges(current);
|
||||
cached_edges.emplace(current, converted);
|
||||
return converted;
|
||||
};
|
||||
const auto result = the_tree_native::search(cells.size(), start_index, goal_indices, edges);
|
||||
output[StringName("status")] = String(result.status.c_str());
|
||||
if (result.status != "usable") return output;
|
||||
PackedInt64Array costs, parents;
|
||||
if (costs.resize(result.costs.size()) != OK || parents.resize(result.parents.size()) != OK) {
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
for (I j = 0; j < cells.size(); ++j) { costs.set(j, result.costs[j]); parents.set(j, result.parents[j]); }
|
||||
TypedArray<int64_t> discovered, parent_discovered;
|
||||
for (I j : result.discovered) discovered.push_back(j);
|
||||
for (I j : result.parent_discovered) parent_discovered.push_back(j);
|
||||
Dictionary settled;
|
||||
for (I j : result.settled) settled[cells[j]] = true;
|
||||
output[StringName("cost_values")] = costs;
|
||||
output[StringName("parent_indices")] = parents;
|
||||
output[StringName("discovered")] = discovered;
|
||||
output[StringName("parent_discovered")] = parent_discovered;
|
||||
output[StringName("settled")] = settled;
|
||||
return output;
|
||||
} catch (const std::exception &) {
|
||||
output = Dictionary();
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void initialize_tree_numeric_core(ModuleInitializationLevel level) {
|
||||
if (level == MODULE_INITIALIZATION_LEVEL_SCENE) GDREGISTER_CLASS(TheTreeNumericCore);
|
||||
}
|
||||
void uninitialize_tree_numeric_core(ModuleInitializationLevel) {}
|
||||
|
||||
extern "C" {
|
||||
GDExtensionBool GDE_EXPORT tree_numeric_core_init(GDExtensionInterfaceGetProcAddress get_proc,
|
||||
GDExtensionClassLibraryPtr library, GDExtensionInitialization *initialization) {
|
||||
GDExtensionBinding::InitObject init(get_proc, library, initialization);
|
||||
init.register_initializer(initialize_tree_numeric_core);
|
||||
init.register_terminator(uninitialize_tree_numeric_core);
|
||||
init.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
return init.init();
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"plan": [
|
||||
[
|
||||
"compile-062",
|
||||
[
|
||||
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++",
|
||||
"-o",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286/objects/062-tree_numeric_core.os",
|
||||
"-c",
|
||||
"-std=c++17",
|
||||
"-isystem",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-candidate-r131/sdk/include",
|
||||
"-isystem",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-candidate-r131/sdk/gen/include",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-Werror",
|
||||
"-fexceptions",
|
||||
"-arch",
|
||||
"arm64",
|
||||
"-isysroot",
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
|
||||
"-fvisibility=hidden",
|
||||
"-O3",
|
||||
"-fPIC",
|
||||
"-DMACOS_ENABLED",
|
||||
"-DUNIX_ENABLED",
|
||||
"-DTHREADS_ENABLED",
|
||||
"-DDEBUG_ENABLED",
|
||||
"-DNDEBUG",
|
||||
"-DGDEXTENSION",
|
||||
"-I/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp"
|
||||
]
|
||||
],
|
||||
[
|
||||
"link",
|
||||
[
|
||||
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++",
|
||||
"-o",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286/libtree_numeric_core.macos.template_debug.dylib",
|
||||
"-arch",
|
||||
"arm64",
|
||||
"-isysroot",
|
||||
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk",
|
||||
"-fvisibility=hidden",
|
||||
"-Wl,-S",
|
||||
"-Wl,-x",
|
||||
"-Wl,-dead_strip",
|
||||
"-shared",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-build-r286/objects/062-tree_numeric_core.os",
|
||||
"/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-sdk-lifetime-build-r133/libgodot-cpp.macos.template_debug.arm64.a"
|
||||
]
|
||||
]
|
||||
],
|
||||
"planned_bridge_compilations": 1,
|
||||
"planned_SDK_compilations": 0,
|
||||
"Godot_loaded": false,
|
||||
"source_candidate": "/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02",
|
||||
"scope": "one bridge compile and link; SDK133 archive reused; no Godot, local or performance acceptance"
|
||||
}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
# coding: utf-8
|
||||
"""Compile only r285-r02 integer Array codec bridge against authenticated unchanged r133 SDK archive; no Godot execution."""
|
||||
from pathlib import Path
|
||||
import importlib.util, json, os, platform, shutil, subprocess, sys, time, signal, shlex
|
||||
sys.dont_write_bytecode=True
|
||||
R=Path(__file__).resolve().parents[2];A=R/'artifacts/original-completion-2026-10-02'
|
||||
C=A/'p23-02-native-integer-window-codec-candidate-r285-r02';O=A/'p23-02-native-integer-window-codec-build-r286'
|
||||
BASE=A/'p23-02-native-bridge-build-r76';BASE_SDK=BASE/'sdk';SDK_C=A/'p23-02-native-sdk-lifetime-candidate-r131';SDK=SDK_C/'sdk';HOST=A/'p23-02-native-sdk-lifetime-build-r133'
|
||||
FLOOR=512*1048576;GROWTH=16*1048576
|
||||
def mod(name,path):
|
||||
spec=importlib.util.spec_from_file_location(name,path);m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m);return m
|
||||
def main():
|
||||
r=mod('buffer_build_runner',R/'tools/run_all_tests.py');outer=mod('buffer_build_owned',r.SUPPORT/'owned_outer.py')
|
||||
build_helper_path=A/'p23-02-native-bridge-build-r76.py'
|
||||
build_helper=mod('retained_build_ownership',build_helper_path)
|
||||
def pin(p):return dict(path=str(p),bytes=p.stat().st_size,sha256=r.sha(p))
|
||||
D=R/'docs/plans/original-completion-2026-10-02';names=['IMPLEMENTATION_PLAN.md','PROGRESS_INTEGRATION.md','work_packages.json','progress_snapshot.json']
|
||||
preexecution_review=A/'p23-02-native-integer-window-codec-build-preexecution-independent-review-r286.json'
|
||||
preexecution=r.read(preexecution_review)
|
||||
assert preexecution['execution_allowed'] and not preexecution['blockers']
|
||||
for group in ['evidence_sha256','canonical_doc4_context_pins']:
|
||||
values=preexecution[group]
|
||||
for q in values.values() if isinstance(values,dict) else values:
|
||||
p=Path(q['path']);assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256']
|
||||
pre_root=r.read(A/'p23-02-native-integer-window-codec-build-preexecution-root-verification-r286.json');assert pre_root['passed'] and pre_root['review_sha256']==r.sha(preexecution_review)
|
||||
source=r.source_refs(R);player=r.default_player(R);docs={n:r.sha(D/n) for n in names}
|
||||
assert {n:v['sha256'] for n,v in source.items()}==r.read(A/'p23-02-final-candidate-r32-source-adoption-r03/source-after.json')
|
||||
assert platform.machine()=='arm64' and not O.exists() and not outer.native.helper['live_engines']()
|
||||
assert shutil.disk_usage(A).free>=FLOOR+GROWTH
|
||||
review=A/'p23-02-native-bridge-build-validation-independent-review-r77.json'
|
||||
assert r.sha(review)=='75d3bbff767fb88af266c4b1b9da37d96294ab72b9e07fcac9a3e7e567a29d49'
|
||||
auth=r.read(review)['evidence_sha256']
|
||||
base_input=BASE/'input.json';archive=BASE_SDK/'bin/libgodot-cpp.macos.template_debug.arm64.a'
|
||||
accepted_receipt=A/'p23-02-native-bridge-build-validation-r77/execution.json'
|
||||
for p in [base_input,archive,accepted_receipt]:
|
||||
v=auth[str(p)];assert p.stat().st_size==v['bytes'] and r.sha(p)==v['sha256']
|
||||
old=r.read(base_input);compiler=Path(old['compiler_pins']['clang++']['path'])
|
||||
assert pin(compiler)==old['compiler_pins']['clang++']
|
||||
for p in old['macos_sdk']['pins']:assert pin(Path(p['path']))==p
|
||||
sdk_path=old['macos_sdk']['path']
|
||||
assert str(Path(subprocess.run(['/usr/bin/xcrun','--sdk','macosx','--show-sdk-path'],capture_output=True,text=True,check=True,timeout=10).stdout.strip()).resolve())==sdk_path
|
||||
candidate_input=C/'input.json';candidate_review=A/'p23-02-native-integer-window-codec-static-independent-review-r285-r02.json'
|
||||
candidate_root=r.read(A/'p23-02-native-integer-window-codec-static-root-verification-r285-r02.json')
|
||||
assert candidate_root['passed'] and candidate_root['review_sha256']==r.sha(candidate_review)
|
||||
v=r.read(candidate_input);support=r.read(candidate_review)
|
||||
assert support['implementation_supported'] and not support['blockers']
|
||||
# Authenticate candidate evidence; current canonical184 context pins must still match at preexecution.
|
||||
for path,q in support['evidence_sha256'].items():
|
||||
p=Path(q.get('path',path));assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256']
|
||||
canonical_context=support['canonical_doc4_context_pins']
|
||||
for q in canonical_context.values() if isinstance(canonical_context,dict) else canonical_context:
|
||||
p=Path(q['path']);assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256']
|
||||
host_review=A/'p23-02-native-sdk-lifetime-build-actual-independent-review-r133.json'
|
||||
assert r.sha(host_review)=='c3cbeb45c1353dfea5f8181d10cc60f8a9b81db55d766ac5db902ac28c159a61'
|
||||
host_support=r.read(host_review);assert host_support['host_build_accepted'] and not host_support['blockers']
|
||||
host_input=HOST/'input.json';new_archive=HOST/'libgodot-cpp.macos.template_debug.arm64.a'
|
||||
for p in [host_input,new_archive]:
|
||||
q=host_support['evidence_sha256'][str(p)];assert p.stat().st_size==q['bytes'] and r.sha(p)==q['sha256']
|
||||
host=r.read(host_input);sdk_inventory=host['SDK_inventory'];assert r.inventory(SDK)==sdk_inventory
|
||||
sdk_candidate_inventory=host['candidate_inventory'];assert r.inventory(SDK_C)==sdk_candidate_inventory
|
||||
accepted_inventory=r.read(accepted_receipt)['original_r76_inventory']
|
||||
parent_sdk_inventory={key[len('sdk/'):]:value for key,value in accepted_inventory.items() if key.startswith('sdk/')}
|
||||
assert parent_sdk_inventory and r.inventory(BASE_SDK)==parent_sdk_inventory
|
||||
source_inventory=r.inventory(C/'source')
|
||||
assert pin(C/'source/tree_numeric_core.cpp')==v['candidate_cpp'] and pin(C/'source/tree_core.hpp')==v['candidate_header']
|
||||
candidate_inventory=r.inventory(C);base_source_inventory=r.inventory(BASE/'source')
|
||||
compile_db=BASE/'compile_commands.json'
|
||||
authenticated_compile_db=auth[str(compile_db)]
|
||||
assert compile_db.stat().st_size==authenticated_compile_db['bytes'] and r.sha(compile_db)==authenticated_compile_db['sha256']
|
||||
commands=r.read(compile_db);assert len(commands)==63
|
||||
fixed=[review,base_input,archive,accepted_receipt,compiler,candidate_input,candidate_review,C/'source/tree_numeric_core.cpp',C/'source/tree_core.hpp',compile_db,host_review,host_input,new_archive,build_helper_path,Path(__file__)]
|
||||
fixed += [A/'p23-02-native-integer-window-codec-static-root-verification-r285-r02.json']
|
||||
fixed += [preexecution_review]
|
||||
fixed += [Path(p['path']) for p in old['macos_sdk']['pins']]
|
||||
pins={str(p):pin(p) for p in fixed}
|
||||
O.mkdir();(O/'objects').mkdir();library=O/'libtree_numeric_core.macos.template_debug.dylib'
|
||||
compile_plan=[];sdk_objects=[];bridge_object=None
|
||||
for i,row in enumerate(commands):
|
||||
if row['file'].startswith('sdk/'):continue
|
||||
argv=shlex.split(row['command']);assert argv[0]=='clang++' and '-c' in argv
|
||||
argv[0]=str(compiler);out_index=argv.index('-o')+1;assert argv[out_index]==row['output']
|
||||
obj=O/'objects'/('%03d-'%i+Path(row['output']).name);argv[out_index]=str(obj)
|
||||
for j,t in enumerate(argv):
|
||||
if t.startswith('sdk/'):argv[j]=str(SDK_C/t)
|
||||
elif t.startswith('source/'):argv[j]=str(C/t)
|
||||
elif t.startswith('-Isdk/'):argv[j]='-I'+str(SDK_C/t[2:])
|
||||
elif t=='-Isource':argv[j]='-I'+str(C/t[2:])
|
||||
source_file=C/row['file'];assert source_file.is_file();pins[str(source_file)]=pin(source_file)
|
||||
compile_plan.append(('compile-%03d'%i,argv))
|
||||
if row['file'].startswith('sdk/'):sdk_objects.append(obj)
|
||||
else:assert row['file']=='source/tree_numeric_core.cpp' and bridge_object is None;bridge_object=obj
|
||||
assert len(sdk_objects)==0 and len(compile_plan)==1 and bridge_object is not None
|
||||
link_argv=[str(compiler),'-o',str(library),'-arch','arm64','-isysroot',sdk_path,'-fvisibility=hidden','-Wl,-S','-Wl,-x','-Wl,-dead_strip','-shared',str(bridge_object),str(new_archive)]
|
||||
plan=compile_plan+[('link',link_argv)]
|
||||
assert json.loads(json.dumps(plan))==r.read(A/'p23-02-native-integer-window-codec-build-preview-r286.json')['plan']
|
||||
r.write(O/'input.json',dict(source_before=source,default_player_before=player,doc4_before=docs,pins=pins,SDK_path=str(SDK),SDK_inventory=sdk_inventory,parent_SDK_inventory=parent_sdk_inventory,SDK_identity_source=pin(accepted_receipt),candidate_inventory=candidate_inventory,base_source_inventory=base_source_inventory,plan=plan,capacity_floor_bytes=FLOOR,maximum_growth_bytes=GROWTH,expected_duration_seconds=[2,30],watchdog_seconds_each=90,serial=True,authenticated_compile_database=authenticated_compile_db,full_input_inventory_before_and_after_each_phase=True,planned_bridge_compilations=1,planned_SDK_compilations=0,SDK_archive_reused=pin(new_archive),old_bridge_object_reused=False,Godot_loaded=False,unit_accepted=False))
|
||||
env=r.fresh_environment(os.environ)
|
||||
for key in list(env):
|
||||
if key.startswith('CCACHE_') or key in ['CFLAGS','CXXFLAGS','CCFLAGS','LDFLAGS','CPATH','CPLUS_INCLUDE_PATH','LIBRARY_PATH','SDKROOT','MACOSX_DEPLOYMENT_TARGET']:
|
||||
env.pop(key)
|
||||
phases=[];error=None;cleanup=[];cleanup_errors=[];binary_checks={};spawned=[]
|
||||
def verify_protected_inputs():
|
||||
assert r.inventory(C)==candidate_inventory
|
||||
assert r.inventory(SDK)==sdk_inventory and r.inventory(SDK_C)==sdk_candidate_inventory
|
||||
assert r.inventory(BASE/'source')==base_source_inventory
|
||||
assert r.inventory(BASE_SDK)==parent_sdk_inventory
|
||||
for path,q in pins.items():assert pin(Path(path))==q
|
||||
|
||||
def run_build_phase(name,argv):
|
||||
# Compiler diagnostics have no Godot banner. Use retained build ownership,
|
||||
# not the engine-specific diagnostic parser or a synthesized banner.
|
||||
folder=O/name;folder.mkdir();process=None;phase_error=None;phase_cleanup=[];errors=[];remaining=None
|
||||
started=time.monotonic();timed_out=False;exit_code=None
|
||||
try:
|
||||
with (folder/'stdout.log').open('wb') as stdout,(folder/'stderr.log').open('wb') as stderr:
|
||||
process=subprocess.Popen(argv,cwd=folder,env=env,stdout=stdout,stderr=stderr,start_new_session=True)
|
||||
spawned.append(dict(phase=name,pid=process.pid,argv=argv))
|
||||
initial=build_helper.group_members(process.pid)
|
||||
if process.poll() is None:
|
||||
assert os.getpgid(process.pid)==process.pid and os.getsid(process.pid)==process.pid
|
||||
assert any(q['pid']==process.pid and q['ppid']==os.getpid() for q in initial)
|
||||
r.write(folder/'started.json',dict(pid=process.pid,owner_pid=os.getpid(),pgid=process.pid,argv=argv,started_utc=r.now(),initial_members=initial,owned_new_session=True))
|
||||
print(json.dumps(dict(build_phase=name,pid=process.pid)),flush=True)
|
||||
while process.poll() is None:
|
||||
if time.monotonic()-started>90:
|
||||
timed_out=True;raise TimeoutError('owned compiler watchdog90s')
|
||||
assert shutil.disk_usage(A).free>=FLOOR
|
||||
assert sum(p.stat().st_size for p in O.rglob('*') if p.is_file())<=GROWTH
|
||||
time.sleep(0.1)
|
||||
exit_code=process.wait(timeout=5)
|
||||
except BaseException as caught:phase_error=type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
if process is not None:
|
||||
try:
|
||||
phase_cleanup,remaining=build_helper.cleanup_owned_parent(process,process.pid,errors)
|
||||
except BaseException as caught:
|
||||
errors.append('retained cleanup exception: '+repr(caught))
|
||||
# An unexpected helper exception never skips exact-Popen cleanup.
|
||||
# No group is discovered or signalled by this fallback.
|
||||
for sig in [signal.SIGTERM,signal.SIGKILL]:
|
||||
try:
|
||||
if process.poll() is None:process.send_signal(sig)
|
||||
except BaseException as failure:errors.append('tracked signal: '+repr(failure))
|
||||
try:process.wait(timeout=3)
|
||||
except BaseException as failure:errors.append('tracked wait: '+repr(failure))
|
||||
try:exit_code=process.wait(timeout=5)
|
||||
except BaseException as caught:errors.append('final tracked wait: '+repr(caught))
|
||||
r.write(folder/'ended.json',dict(pid=process.pid,argv=argv,pgid=process.pid,ended_utc=r.now(),exit_code=exit_code,duration_seconds=time.monotonic()-started,cleanup=phase_cleanup,remaining=remaining,cleanup_errors=errors,foreign_process_or_group_signals=0))
|
||||
result=dict(pid=process.pid if process else None,exit_code=exit_code,timed_out=timed_out,error_lines=[phase_error] if phase_error else [],duration_seconds=time.monotonic()-started)
|
||||
capture=dict(passed=process is not None and phase_error is None and not errors and remaining==[],cleanup=phase_cleanup,cleanup_errors=errors,all_captured_ended=process is not None and process.poll() is not None,foreign_process_or_group_signals=0)
|
||||
return result,capture
|
||||
try:
|
||||
for name,argv in plan:
|
||||
assert shutil.disk_usage(A).free>=FLOOR+GROWTH and not outer.native.helper['live_engines']()
|
||||
# Authenticate every header/source and retained parent before and after each owned phase.
|
||||
verify_protected_inputs()
|
||||
result,capture=run_build_phase(name,argv)
|
||||
post_input_error=None
|
||||
try:verify_protected_inputs()
|
||||
except BaseException as caught:post_input_error=type(caught).__name__+': '+str(caught)
|
||||
row=dict(name=name,result=result,capture=capture,full_input_inventory_before_verified=True,full_input_inventory_after_verified=post_input_error is None,post_input_error=post_input_error);phases.append(row);r.write(O/(name+'-phase.json'),row)
|
||||
assert post_input_error is None, post_input_error
|
||||
assert result is not None and result['exit_code']==0 and not result['error_lines'] and not result['timed_out'] and capture['passed']
|
||||
assert (O/name/'stderr.log').read_bytes()==b'' and (O/name/'stdout.log').read_bytes()==b''
|
||||
assert sum(p.stat().st_size for p in O.rglob('*') if p.is_file())<=GROWTH
|
||||
assert library.is_file() and bridge_object.is_file() and new_archive.is_file()
|
||||
for name,argv in [('file',['/usr/bin/file',str(library)]),('arch',['/usr/bin/lipo','-archs',str(library)]),('dependencies',['/usr/bin/otool','-L',str(library)]),('exports',['/usr/bin/nm','-gU',str(library)])]:
|
||||
q=subprocess.run(argv,capture_output=True,text=True,check=True,timeout=10)
|
||||
assert not q.stderr;binary_checks[name]=q.stdout
|
||||
assert binary_checks['arch'].strip()=='arm64' and '_tree_numeric_core_init' in binary_checks['exports'] and 'dynamically linked shared library' in binary_checks['file']
|
||||
except BaseException as caught:error=type(caught).__name__+': '+str(caught)
|
||||
finally:
|
||||
cleanup=[row['capture']['cleanup'] for row in phases]
|
||||
cleanup_errors=[message for row in phases for message in row['capture']['cleanup_errors']]
|
||||
preserved=r.source_refs(R)==source and r.default_player(R)==player and {n:r.sha(D/n) for n in names}==docs and r.inventory(C)==candidate_inventory and r.inventory(SDK)==sdk_inventory and r.inventory(SDK_C)==sdk_candidate_inventory and r.inventory(BASE/'source')==base_source_inventory and r.inventory(BASE_SDK)==parent_sdk_inventory
|
||||
for name_pin,q in pins.items():preserved=preserved and pin(Path(name_pin))==q
|
||||
assert preserved
|
||||
passed=error is None and len(phases)==2 and not cleanup_errors
|
||||
r.write(O/'execution.json',dict(passed=passed,error=error,phases=phases,cleanup=cleanup,cleanup_errors=cleanup_errors,binary_checks=binary_checks,object=pin(bridge_object) if bridge_object.exists() else None,library=pin(library) if library.exists() else None,original_inputs_preserved=preserved,bridge_compilations=sum(q['phase']=='compile-062' for q in spawned),link_attempts=sum(q['phase']=='link' for q in spawned),spawned_children=spawned,SDK_compilations=sum(q['phase'].startswith('compile-') and q['phase']!='compile-062' for q in spawned),SDK_archive_reused=pin(new_archive),SDK_archive_rebuilt=False,old_bridge_object_reused=False,Godot_loaded=False,selected=False,unit_accepted=False,performance_accepted=False,limits=['Exact modified r285-r02 integer Array codec bridge one-object compile and link to authenticated unchanged r133 SDK archive only; no SDK recompilation, Godot load, protocol, performance or unit acceptance.']))
|
||||
print(json.dumps(dict(passed=passed,error=error)),flush=True);return 0 if passed else 1
|
||||
if __name__=='__main__':raise SystemExit(main())
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
# coding: utf-8
|
||||
from pathlib import Path
|
||||
import hashlib,json,shutil
|
||||
R=Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');A=R/'artifacts/original-completion-2026-10-02';C=A/'p23-02-native-integer-window-codec-candidate-r285-r02';P=A/'p23-02-native-pure-tag-projection-candidate-r274/source';assert not C.exists();assert shutil.disk_usage(A).free>=528*1048576
|
||||
(C/'source').mkdir(parents=True);original=(P/'tree_numeric_core.cpp').read_text();text=original
|
||||
helpers=r'''
|
||||
// Godot ed1daf0bf's integer Array wire layout; checked against this engine
|
||||
// once per core, never sample values. Every caller-owned Array stays intact.
|
||||
bool integer_wire_checked=false;
|
||||
bool integer_wire_compatible=false;
|
||||
static void wire32(uint8_t *p,uint32_t value) {
|
||||
for (unsigned i=0;i<4;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static void wire64(uint8_t *p,uint64_t value) {
|
||||
for (unsigned i=0;i<8;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static int64_t integer_wire_width(int64_t value) {
|
||||
return value<int64_t(INT32_MIN) || value>int64_t(INT32_MAX) ? 12 : 8;
|
||||
}
|
||||
static void wire_integer(uint8_t *p,int64_t value) {
|
||||
const bool wide=integer_wire_width(value)==12;
|
||||
wire32(p,uint32_t(Variant::INT) | (wide ? uint32_t(1)<<16 : 0));
|
||||
if (wide) wire64(p+4,uint64_t(value)); else wire32(p+4,uint32_t(value));
|
||||
}
|
||||
static bool integer_wire_encode(const Array &values,PackedByteArray &out) {
|
||||
const bool typed=values.is_typed();
|
||||
if (typed && (values.get_typed_builtin()!=Variant::INT ||
|
||||
!values.get_typed_class_name().is_empty() || values.get_typed_script().get_type()!=Variant::NIL)) return false;
|
||||
const int64_t count=values.size(),header=typed ? 12 : 8;
|
||||
// The engine encoder uses a signed-int byte length. Preserve fallback
|
||||
// for oversized inputs and failed allocations, before owner mutation.
|
||||
if (count<0 || count>(int64_t(INT32_MAX)-header)/12) return false;
|
||||
int64_t size=header;
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const Variant value=values[i];
|
||||
if (value.get_type()!=Variant::INT) return false;
|
||||
const int64_t integer=value;
|
||||
size+=integer_wire_width(integer);
|
||||
}
|
||||
if (out.resize(size)!=OK) return false;
|
||||
uint8_t *p=out.ptrw();
|
||||
wire32(p,uint32_t(Variant::ARRAY) | (typed ? uint32_t(1)<<16 : 0));
|
||||
int64_t offset=4;
|
||||
if (typed) {wire32(p+offset,uint32_t(Variant::INT));offset+=4;}
|
||||
wire32(p+offset,uint32_t(count));offset+=4;
|
||||
// Integer-only synchronous values have no callback during either pass.
|
||||
// Use the engine-owned Array directly, with no throwing vector allocation.
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const int64_t value=values[i];
|
||||
wire_integer(p+offset,value);offset+=integer_wire_width(value);
|
||||
}
|
||||
return offset==size;
|
||||
}
|
||||
bool integer_wire_ready() {
|
||||
if (integer_wire_checked) return integer_wire_compatible;
|
||||
Array empty,probe;
|
||||
TypedArray<int64_t> typed_empty,typed_probe;
|
||||
const int64_t boundaries[]={INT64_MIN,int64_t(INT32_MIN)-1,INT32_MIN,-1,0,1,INT32_MAX,int64_t(INT32_MAX)+1,INT64_MAX};
|
||||
for (const int64_t value: boundaries) {probe.append(value);typed_probe.append(value);}
|
||||
bool matches=true;
|
||||
for (const Array &value: {empty,probe,Array(typed_empty),Array(typed_probe)}) {
|
||||
PackedByteArray encoded;
|
||||
matches=integer_wire_encode(value,encoded) && encoded==UtilityFunctions::var_to_bytes(value) && matches;
|
||||
}
|
||||
integer_wire_compatible=matches;integer_wire_checked=true;
|
||||
return matches;
|
||||
}
|
||||
static bool integer_wire_update(const PackedByteArray &before,bool typed,int64_t old_count,
|
||||
int64_t appended,bool pop_front,PackedByteArray &after) {
|
||||
const int64_t header=typed ? 12 : 8;
|
||||
if (before.size()<header || old_count<0) return false;
|
||||
const bool remove_appended=pop_front && old_count==0;
|
||||
int64_t skipped=0;
|
||||
if (pop_front && old_count>0) {
|
||||
if (before.size()<header+8) return false;
|
||||
// Exact INT header was produced locally from the current values.
|
||||
skipped=(before[header+2] & 1) ? 12 : 8;
|
||||
if (before.size()<header+skipped) return false;
|
||||
}
|
||||
const int64_t added=remove_appended ? 0 : integer_wire_width(appended);
|
||||
const int64_t size=before.size()-skipped+added;
|
||||
if (size>INT32_MAX || after.resize(size)!=OK) return false;
|
||||
uint8_t *p=after.ptrw();const uint8_t *q=before.ptr();
|
||||
std::memcpy(p,q,size_t(header));
|
||||
wire32(p+header-4,uint32_t(old_count+1-(pop_front ? 1 : 0)));
|
||||
const int64_t kept=before.size()-header-skipped;
|
||||
if (kept>0) std::memcpy(p+header,q+header+skipped,size_t(kept));
|
||||
if (added>0) wire_integer(p+header+kept,appended);
|
||||
return true;
|
||||
}
|
||||
'''
|
||||
text=text.replace('#include <cmath>','#include <cmath>\n#include <cstdint>\n#include <cstring>',1)
|
||||
needle=' std::map<I, std::vector<Edge>> cached_edges;';assert text.count(needle)==1;text=text.replace(needle,needle+helpers,1)
|
||||
needle=' ClassDB::bind_method(D_METHOD("performance_record_sample", "owner", "metric_id", "duration_us"), &TheTreeNumericCore::performance_record_sample);';assert text.count(needle)==1
|
||||
text=text.replace(needle,needle+'\n ClassDB::bind_method(D_METHOD("encode_integer_window", "values"), &TheTreeNumericCore::encode_integer_window);',1)
|
||||
needle=' // Whole builtin integer record operation.'
|
||||
method=''' Dictionary encode_integer_window(const Array &values) {
|
||||
Dictionary result;const StringName supported("supported",false),bytes("bytes",false);
|
||||
result[supported]=false;PackedByteArray encoded;
|
||||
if (!integer_wire_ready() || !integer_wire_encode(values,encoded)) return result;
|
||||
result[supported]=true;result[bytes]=encoded;return result;
|
||||
}
|
||||
'''
|
||||
assert text.count(needle)==1;text=text.replace(needle,method+needle,1)
|
||||
old=' for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;';assert text.count(old)==1;text=text.replace(old,'',1)
|
||||
old=' PackedByteArray before=UtilityFunctions::var_to_bytes(values);';assert text.count(old)==1
|
||||
new=''' PackedByteArray before;
|
||||
const bool fast_wire=integer_wire_ready() && integer_wire_encode(values,before);
|
||||
if (!fast_wire) {
|
||||
for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;
|
||||
before=UtilityFunctions::var_to_bytes(values);
|
||||
}
|
||||
const int64_t old_count=values.size();
|
||||
''';text=text.replace(old,new.rstrip(),1)
|
||||
old=' PackedByteArray after=UtilityFunctions::var_to_bytes(values);';assert text.count(old)==1
|
||||
new=''' PackedByteArray after;
|
||||
const bool popped=old_count+1>int64_t(capacityv);
|
||||
if (!fast_wire || !integer_wire_update(before,values.is_typed(),old_count,duration,popped,after))
|
||||
after=UtilityFunctions::var_to_bytes(values);''';text=text.replace(old,new,1)
|
||||
(C/'source/tree_numeric_core.cpp').write_text(text);shutil.copyfile(P/'tree_core.hpp',C/'source/tree_core.hpp')
|
||||
def pin(p):b=p.read_bytes();return dict(path=str(p),bytes=len(b),sha256=hashlib.sha256(b).hexdigest())
|
||||
D=R/'docs/plans/original-completion-2026-10-02'
|
||||
with (C/'input.json').open('x') as f:json.dump(dict(parent_cpp=pin(P/'tree_numeric_core.cpp'),parent_header=pin(P/'tree_core.hpp'),candidate_cpp=pin(C/'source/tree_numeric_core.cpp'),candidate_header=pin(C/'source/tree_core.hpp'),author=pin(Path(__file__)),primary_encoder=pin(A/'p23-02-integer-window-codec-source-r284/marshalls.cpp'),primary_encoder_reference=pin(A/'p23-02-integer-window-codec-source-r284/source.json'),budget=pin(A/'p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd'),ledger_review=pin(A/'p23-02-progress-integration-actual-independent-review-r282.json'),doc4={n:pin(D/n) for n in ['IMPLEMENTATION_PLAN.md','PROGRESS_INTEGRATION.md','work_packages.json','progress_snapshot.json']},scope='source-only integer Array exact bytes encode and append/pop transformation; per-object engine format check, no cached sample values; old encoding fallback retained; report/values/thresholds/notifications/typed/ownership unchanged; performance and load unproven',engine_runs=0,compiler_runs=0,stage_clones=0,candidate_adopted=False,unit_accepted=False),f,ensure_ascii=False,indent=2)
|
||||
print('source candidate prepared; source-only, no build or engine')
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
# coding: utf-8
|
||||
from pathlib import Path
|
||||
import hashlib,json,shutil
|
||||
R=Path('/Users/korizatou114/Projects/とりにく工房ゲーム制作部');A=R/'artifacts/original-completion-2026-10-02';C=A/'p23-02-native-integer-window-codec-candidate-r285';P=A/'p23-02-native-pure-tag-projection-candidate-r274/source';assert not C.exists();assert shutil.disk_usage(A).free>=528*1048576
|
||||
(C/'source').mkdir(parents=True);original=(P/'tree_numeric_core.cpp').read_text();text=original
|
||||
helpers=r'''
|
||||
// Godot ed1daf0bf's integer Array wire layout; checked against this engine
|
||||
// once per core, never sample values. Every caller-owned Array stays intact.
|
||||
bool integer_wire_checked=false;
|
||||
bool integer_wire_compatible=false;
|
||||
static void wire32(uint8_t *p,uint32_t value) {
|
||||
for (unsigned i=0;i<4;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static void wire64(uint8_t *p,uint64_t value) {
|
||||
for (unsigned i=0;i<8;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static int64_t integer_wire_width(int64_t value) {
|
||||
return value<int64_t(INT32_MIN) || value>int64_t(INT32_MAX) ? 12 : 8;
|
||||
}
|
||||
static void wire_integer(uint8_t *p,int64_t value) {
|
||||
const bool wide=integer_wire_width(value)==12;
|
||||
wire32(p,uint32_t(Variant::INT) | (wide ? uint32_t(1)<<16 : 0));
|
||||
if (wide) wire64(p+4,uint64_t(value)); else wire32(p+4,uint32_t(value));
|
||||
}
|
||||
static bool integer_wire_encode(const Array &values,PackedByteArray &out) {
|
||||
const bool typed=values.is_typed();
|
||||
if (typed && (values.get_typed_builtin()!=Variant::INT ||
|
||||
!values.get_typed_class_name().is_empty() || values.get_typed_script().get_type()!=Variant::NIL)) return false;
|
||||
const int64_t count=values.size(),header=typed ? 12 : 8;
|
||||
// The engine encoder uses a signed-int byte length. Preserve fallback
|
||||
// for oversized inputs and failed allocations, before owner mutation.
|
||||
if (count<0 || count>(int64_t(INT32_MAX)-header)/12) return false;
|
||||
std::vector<int64_t> integers;
|
||||
integers.reserve(size_t(count));
|
||||
int64_t size=header;
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const Variant value=values[i];
|
||||
if (value.get_type()!=Variant::INT) return false;
|
||||
const int64_t integer=value;
|
||||
integers.push_back(integer);size+=integer_wire_width(integer);
|
||||
}
|
||||
if (out.resize(size)!=OK) return false;
|
||||
uint8_t *p=out.ptrw();
|
||||
wire32(p,uint32_t(Variant::ARRAY) | (typed ? uint32_t(1)<<16 : 0));
|
||||
int64_t offset=4;
|
||||
if (typed) {wire32(p+offset,uint32_t(Variant::INT));offset+=4;}
|
||||
wire32(p+offset,uint32_t(count));offset+=4;
|
||||
for (const int64_t value: integers) {wire_integer(p+offset,value);offset+=integer_wire_width(value);}
|
||||
return offset==size;
|
||||
}
|
||||
bool integer_wire_ready() {
|
||||
if (integer_wire_checked) return integer_wire_compatible;
|
||||
Array empty,probe;
|
||||
TypedArray<int64_t> typed_empty,typed_probe;
|
||||
const int64_t boundaries[]={INT64_MIN,int64_t(INT32_MIN)-1,INT32_MIN,-1,0,1,INT32_MAX,int64_t(INT32_MAX)+1,INT64_MAX};
|
||||
for (const int64_t value: boundaries) {probe.append(value);typed_probe.append(value);}
|
||||
bool matches=true;
|
||||
for (const Array &value: {empty,probe,Array(typed_empty),Array(typed_probe)}) {
|
||||
PackedByteArray encoded;
|
||||
matches=integer_wire_encode(value,encoded) && encoded==UtilityFunctions::var_to_bytes(value) && matches;
|
||||
}
|
||||
integer_wire_compatible=matches;integer_wire_checked=true;
|
||||
return matches;
|
||||
}
|
||||
static bool integer_wire_update(const PackedByteArray &before,bool typed,int64_t old_count,
|
||||
int64_t appended,bool pop_front,PackedByteArray &after) {
|
||||
const int64_t header=typed ? 12 : 8;
|
||||
if (before.size()<header || old_count<0) return false;
|
||||
const bool remove_appended=pop_front && old_count==0;
|
||||
int64_t skipped=0;
|
||||
if (pop_front && old_count>0) {
|
||||
if (before.size()<header+8) return false;
|
||||
// Exact INT header was produced locally from the current values.
|
||||
skipped=(before[header+2] & 1) ? 12 : 8;
|
||||
if (before.size()<header+skipped) return false;
|
||||
}
|
||||
const int64_t added=remove_appended ? 0 : integer_wire_width(appended);
|
||||
const int64_t size=before.size()-skipped+added;
|
||||
if (size>INT32_MAX || after.resize(size)!=OK) return false;
|
||||
uint8_t *p=after.ptrw();const uint8_t *q=before.ptr();
|
||||
std::memcpy(p,q,size_t(header));
|
||||
wire32(p+header-4,uint32_t(old_count+1-(pop_front ? 1 : 0)));
|
||||
const int64_t kept=before.size()-header-skipped;
|
||||
if (kept>0) std::memcpy(p+header,q+header+skipped,size_t(kept));
|
||||
if (added>0) wire_integer(p+header+kept,appended);
|
||||
return true;
|
||||
}
|
||||
'''
|
||||
text=text.replace('#include <cmath>','#include <cmath>\n#include <cstdint>\n#include <cstring>',1)
|
||||
needle=' std::map<I, std::vector<Edge>> cached_edges;';assert text.count(needle)==1;text=text.replace(needle,needle+helpers,1)
|
||||
needle=' ClassDB::bind_method(D_METHOD("performance_record_sample", "owner", "metric_id", "duration_us"), &TheTreeNumericCore::performance_record_sample);';assert text.count(needle)==1
|
||||
text=text.replace(needle,needle+'\n ClassDB::bind_method(D_METHOD("encode_integer_window", "values"), &TheTreeNumericCore::encode_integer_window);',1)
|
||||
needle=' // Whole builtin integer record operation.'
|
||||
method=''' Dictionary encode_integer_window(const Array &values) {
|
||||
Dictionary result;const StringName supported("supported",false),bytes("bytes",false);
|
||||
result[supported]=false;PackedByteArray encoded;
|
||||
if (!integer_wire_ready() || !integer_wire_encode(values,encoded)) return result;
|
||||
result[supported]=true;result[bytes]=encoded;return result;
|
||||
}
|
||||
'''
|
||||
assert text.count(needle)==1;text=text.replace(needle,method+needle,1)
|
||||
old=' for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;';assert text.count(old)==1;text=text.replace(old,'',1)
|
||||
old=' PackedByteArray before=UtilityFunctions::var_to_bytes(values);';assert text.count(old)==1
|
||||
new=''' PackedByteArray before;
|
||||
const bool fast_wire=integer_wire_ready() && integer_wire_encode(values,before);
|
||||
if (!fast_wire) {
|
||||
for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;
|
||||
before=UtilityFunctions::var_to_bytes(values);
|
||||
}
|
||||
const int64_t old_count=values.size();
|
||||
''';text=text.replace(old,new.rstrip(),1)
|
||||
old=' PackedByteArray after=UtilityFunctions::var_to_bytes(values);';assert text.count(old)==1
|
||||
new=''' PackedByteArray after;
|
||||
const bool popped=old_count+1>int64_t(capacityv);
|
||||
if (!fast_wire || !integer_wire_update(before,values.is_typed(),old_count,duration,popped,after))
|
||||
after=UtilityFunctions::var_to_bytes(values);''';text=text.replace(old,new,1)
|
||||
(C/'source/tree_numeric_core.cpp').write_text(text);shutil.copyfile(P/'tree_core.hpp',C/'source/tree_core.hpp')
|
||||
def pin(p):b=p.read_bytes();return dict(path=str(p),bytes=len(b),sha256=hashlib.sha256(b).hexdigest())
|
||||
D=R/'docs/plans/original-completion-2026-10-02'
|
||||
with (C/'input.json').open('x') as f:json.dump(dict(parent_cpp=pin(P/'tree_numeric_core.cpp'),parent_header=pin(P/'tree_core.hpp'),candidate_cpp=pin(C/'source/tree_numeric_core.cpp'),candidate_header=pin(C/'source/tree_core.hpp'),author=pin(Path(__file__)),primary_encoder=pin(A/'p23-02-integer-window-codec-source-r284/marshalls.cpp'),primary_encoder_reference=pin(A/'p23-02-integer-window-codec-source-r284/source.json'),budget=pin(A/'p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd'),ledger_review=pin(A/'p23-02-progress-integration-actual-independent-review-r282.json'),doc4={n:pin(D/n) for n in ['IMPLEMENTATION_PLAN.md','PROGRESS_INTEGRATION.md','work_packages.json','progress_snapshot.json']},scope='source-only integer Array exact bytes encode and append/pop transformation; per-object engine format check, no cached sample values; old encoding fallback retained; report/values/thresholds/notifications/typed/ownership unchanged; performance and load unproven',engine_runs=0,compiler_runs=0,stage_clones=0,candidate_adopted=False,unit_accepted=False),f,ensure_ascii=False,indent=2)
|
||||
print('source candidate prepared; source-only, no build or engine')
|
||||
+2155
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"url": "https://raw.githubusercontent.com/godotengine/godot/ed1daf0bf/core/io/marshalls.cpp",
|
||||
"engine_revision": "ed1daf0bf",
|
||||
"source": {
|
||||
"path": "/Users/korizatou114/Projects/\u3068\u308a\u306b\u304f\u5de5\u623f\u30b2\u30fc\u30e0\u5236\u4f5c\u90e8/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
"scope": "Primary encoder source only; local format equivalence not yet executed",
|
||||
"implementation_accepted": false
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"parent_cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_numeric_core.cpp",
|
||||
"bytes": 54002,
|
||||
"sha256": "ce20a90adb8a410dc1b6cbf607b7bfa8bf21eaef158c5c6bfc7a9bbcc703d2b2"
|
||||
},
|
||||
"parent_header": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-pure-tag-projection-candidate-r274/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"candidate_cpp": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_numeric_core.cpp",
|
||||
"bytes": 59396,
|
||||
"sha256": "75bc2f659f62889fccac5990461f5b51e8b87f30ca0791e4a4c542d37a7c5236"
|
||||
},
|
||||
"candidate_header": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-integer-window-codec-candidate-r285-r02/source/tree_core.hpp",
|
||||
"bytes": 3299,
|
||||
"sha256": "be46852fe55aeee4e18996b4d87222662b031fd1b946977c81092f4d0707e480"
|
||||
},
|
||||
"author": {
|
||||
"path": "/private/tmp/thetree-prepare-integer-window-codec-r285-r02.py",
|
||||
"bytes": 8607,
|
||||
"sha256": "7316b5b10bc65f7e286b66940767175f1f6b07182b84c8c2b326907bc668f9b5"
|
||||
},
|
||||
"primary_encoder": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/marshalls.cpp",
|
||||
"bytes": 55439,
|
||||
"sha256": "183c43e46d3e4b73c0fb1fc8018da37faeb88c7414dd928b1fb6ff4f4a91a374"
|
||||
},
|
||||
"primary_encoder_reference": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-integer-window-codec-source-r284/source.json",
|
||||
"bytes": 585,
|
||||
"sha256": "cbdfe256f152bc8f0c44b849848a94e3eec1e84d6a55f5da69188a6430a148a9"
|
||||
},
|
||||
"budget": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-native-budget-record-candidate-r257-r04/performance_budget.candidate.gd",
|
||||
"bytes": 11250,
|
||||
"sha256": "1010129d3124c3aec037c840a5766a7cdabe60c654999dfb6f23b224140a73f8"
|
||||
},
|
||||
"ledger_review": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/artifacts/original-completion-2026-10-02/p23-02-progress-integration-actual-independent-review-r282.json",
|
||||
"bytes": 14529,
|
||||
"sha256": "40b90fe5086d1bb468f14418657b946fac11615b46cdf66de5f0ccac832a8457"
|
||||
},
|
||||
"doc4": {
|
||||
"IMPLEMENTATION_PLAN.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/IMPLEMENTATION_PLAN.md",
|
||||
"bytes": 492762,
|
||||
"sha256": "c2f4824ac3f47231395820ef792d88884a6d7c578f9f6c7b1379619c25fe7a01"
|
||||
},
|
||||
"PROGRESS_INTEGRATION.md": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/PROGRESS_INTEGRATION.md",
|
||||
"bytes": 410826,
|
||||
"sha256": "9eaf70befb9b94d0b890caef1d8afd8f6348a3b6621fd03d5fd469f9638806c0"
|
||||
},
|
||||
"work_packages.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/work_packages.json",
|
||||
"bytes": 1180547,
|
||||
"sha256": "f6538388b31fecba52276fe90dca327afc00a54ea530a8b4ecddfc87fd32ae7e"
|
||||
},
|
||||
"progress_snapshot.json": {
|
||||
"path": "/Users/korizatou114/Projects/とりにく工房ゲーム制作部/docs/plans/original-completion-2026-10-02/progress_snapshot.json",
|
||||
"bytes": 529159,
|
||||
"sha256": "9a03b385c8e53570012a0e3b977a8c78a3cec11ea8d5f585841f5abff3c19517"
|
||||
}
|
||||
},
|
||||
"scope": "source-only integer Array exact bytes encode and append/pop transformation; per-object engine format check, no cached sample values; old encoding fallback retained; report/values/thresholds/notifications/typed/ownership unchanged; performance and load unproven",
|
||||
"engine_runs": 0,
|
||||
"compiler_runs": 0,
|
||||
"stage_clones": 0,
|
||||
"candidate_adopted": false,
|
||||
"unit_accepted": false
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
// Standalone numeric prototype; not connected to Godot or production.
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace the_tree_native {
|
||||
using I = std::int64_t;
|
||||
struct Edge { I neighbor, cost; };
|
||||
struct Result {
|
||||
std::string status = "usable";
|
||||
std::vector<I> costs, parents, discovered, parent_discovered, settled;
|
||||
};
|
||||
I signed_add(I a, I b) {
|
||||
const auto sum = static_cast<std::uint64_t>(a) + static_cast<std::uint64_t>(b);
|
||||
if (sum <= static_cast<std::uint64_t>(std::numeric_limits<I>::max())) return static_cast<I>(sum);
|
||||
return std::numeric_limits<I>::min() + static_cast<I>(sum - (std::uint64_t(1) << 63));
|
||||
}
|
||||
template <typename EdgeProvider>
|
||||
Result search(I n, I start, const std::vector<I>& goals, EdgeProvider&& edge_provider) {
|
||||
Result r;
|
||||
if (start < 0 || start >= n) { r.status = "unusable"; return r; }
|
||||
r.costs.assign(n, std::numeric_limits<I>::max());
|
||||
r.parents.assign(n, -1);
|
||||
std::vector<unsigned char> finalized(n, 0), pending(n, 0);
|
||||
bool unknown_goal = false;
|
||||
I remaining = 0;
|
||||
for (I goal : goals) {
|
||||
if (goal < 0 || goal >= n) { unknown_goal = true; continue; }
|
||||
if (!pending[goal]) { pending[goal] = 1; ++remaining; }
|
||||
}
|
||||
r.costs[start] = 0; r.discovered.push_back(start);
|
||||
std::map<I, std::vector<I>> frontier;
|
||||
frontier[0].push_back(start);
|
||||
while (!frontier.empty()) {
|
||||
auto it = frontier.begin();
|
||||
const I entry_cost = it->first;
|
||||
auto active = std::move(it->second);
|
||||
frontier.erase(it);
|
||||
std::sort(active.begin(), active.end());
|
||||
for (I current : active) {
|
||||
if (finalized[current] || entry_cost != r.costs[current]) continue;
|
||||
finalized[current] = 1;
|
||||
r.settled.push_back(current);
|
||||
if (pending[current]) { pending[current] = 0; --remaining; }
|
||||
if (!remaining && !unknown_goal) return r;
|
||||
for (const Edge& edge : edge_provider(current)) {
|
||||
if (edge.cost <= 0) { r.status = "unusable"; return r; }
|
||||
const I neighbor = edge.neighbor;
|
||||
if (neighbor < 0 || neighbor >= n) { r.status = "unusable"; return r; }
|
||||
const I cost = signed_add(r.costs[current], edge.cost);
|
||||
if (cost < r.costs[current]) { r.status = "wrapped"; return r; }
|
||||
const I known = r.costs[neighbor];
|
||||
const bool better_parent = cost == known && r.parents[neighbor] >= 0 && current < r.parents[neighbor];
|
||||
if (cost > known || (cost == known && !better_parent)) continue;
|
||||
if (r.parents[neighbor] < 0) {
|
||||
r.parent_discovered.push_back(neighbor);
|
||||
if (neighbor != start) r.discovered.push_back(neighbor);
|
||||
}
|
||||
r.parents[neighbor] = current;
|
||||
r.costs[neighbor] = cost;
|
||||
if (finalized[neighbor]) r.settled.erase(std::remove(r.settled.begin(), r.settled.end(), neighbor), r.settled.end());
|
||||
finalized[neighbor] = 0;
|
||||
frontier[cost].push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
} // namespace the_tree_native
|
||||
+926
@@ -0,0 +1,926 @@
|
||||
// Isolated, uncompiled bridge prototype. No production registration or integration yet.
|
||||
#include "tree_core.hpp"
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <godot_cpp/classes/ref_counted.hpp>
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/variant/vector3i.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
#include <godot_cpp/variant/array.hpp>
|
||||
#include <godot_cpp/variant/callable.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
#include <godot_cpp/variant/packed_int64_array.hpp>
|
||||
#include <godot_cpp/variant/typed_array.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
using namespace godot;
|
||||
using the_tree_native::I;
|
||||
using the_tree_native::Edge;
|
||||
|
||||
class TheTreeNumericCore : public RefCounted {
|
||||
GDCLASS(TheTreeNumericCore, RefCounted)
|
||||
protected:
|
||||
static void _bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("performance_record_sample", "owner", "metric_id", "duration_us"), &TheTreeNumericCore::performance_record_sample);
|
||||
ClassDB::bind_method(D_METHOD("encode_integer_window", "values"), &TheTreeNumericCore::encode_integer_window);
|
||||
ClassDB::bind_method(D_METHOD("guest_tree_edge_rows", "path", "current", "layers", "access"), &TheTreeNumericCore::guest_tree_edge_rows);
|
||||
ClassDB::bind_method(D_METHOD("search", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search);
|
||||
ClassDB::bind_method(D_METHOD("search_cached", "cells", "indices", "start_index", "goals", "edge_provider"), &TheTreeNumericCore::search_cached);
|
||||
ClassDB::bind_method(D_METHOD("clear_edge_cache"), &TheTreeNumericCore::clear_edge_cache);
|
||||
ClassDB::bind_method(D_METHOD("nearest_guest_access", "path", "start", "goals", "fallback_target", "layers", "access", "ticks"), &TheTreeNumericCore::nearest_guest_access);
|
||||
ClassDB::bind_method(D_METHOD("project_guest_candidates", "runtime", "guest_id", "open_state", "degraded_state", "movement_scale", "stair_credit", "orthogonal_cost", "ticks"), &TheTreeNumericCore::project_guest_candidates);
|
||||
}
|
||||
private:
|
||||
// Cache belongs to this instance and one caller-owned graph workspace.
|
||||
// No reuse after cells, indices, filters or edge values change: the caller
|
||||
// must clear it or release this core. The original search never uses it.
|
||||
std::map<I, std::vector<Edge>> cached_edges;
|
||||
// Godot ed1daf0bf's integer Array wire layout; checked against this engine
|
||||
// once per core, never sample values. Every caller-owned Array stays intact.
|
||||
bool integer_wire_checked=false;
|
||||
bool integer_wire_compatible=false;
|
||||
static void wire32(uint8_t *p,uint32_t value) {
|
||||
for (unsigned i=0;i<4;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static void wire64(uint8_t *p,uint64_t value) {
|
||||
for (unsigned i=0;i<8;++i) p[i]=uint8_t(value>>(8*i));
|
||||
}
|
||||
static int64_t integer_wire_width(int64_t value) {
|
||||
return value<int64_t(INT32_MIN) || value>int64_t(INT32_MAX) ? 12 : 8;
|
||||
}
|
||||
static void wire_integer(uint8_t *p,int64_t value) {
|
||||
const bool wide=integer_wire_width(value)==12;
|
||||
wire32(p,uint32_t(Variant::INT) | (wide ? uint32_t(1)<<16 : 0));
|
||||
if (wide) wire64(p+4,uint64_t(value)); else wire32(p+4,uint32_t(value));
|
||||
}
|
||||
static bool integer_wire_encode(const Array &values,PackedByteArray &out) {
|
||||
const bool typed=values.is_typed();
|
||||
if (typed && (values.get_typed_builtin()!=Variant::INT ||
|
||||
!values.get_typed_class_name().is_empty() || values.get_typed_script().get_type()!=Variant::NIL)) return false;
|
||||
const int64_t count=values.size(),header=typed ? 12 : 8;
|
||||
// The engine encoder uses a signed-int byte length. Preserve fallback
|
||||
// for oversized inputs and failed allocations, before owner mutation.
|
||||
if (count<0 || count>(int64_t(INT32_MAX)-header)/12) return false;
|
||||
int64_t size=header;
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const Variant value=values[i];
|
||||
if (value.get_type()!=Variant::INT) return false;
|
||||
const int64_t integer=value;
|
||||
size+=integer_wire_width(integer);
|
||||
}
|
||||
if (out.resize(size)!=OK) return false;
|
||||
uint8_t *p=out.ptrw();
|
||||
wire32(p,uint32_t(Variant::ARRAY) | (typed ? uint32_t(1)<<16 : 0));
|
||||
int64_t offset=4;
|
||||
if (typed) {wire32(p+offset,uint32_t(Variant::INT));offset+=4;}
|
||||
wire32(p+offset,uint32_t(count));offset+=4;
|
||||
// Integer-only synchronous values have no callback during either pass.
|
||||
// Use the engine-owned Array directly, with no throwing vector allocation.
|
||||
for (int64_t i=0;i<count;++i) {
|
||||
const int64_t value=values[i];
|
||||
wire_integer(p+offset,value);offset+=integer_wire_width(value);
|
||||
}
|
||||
return offset==size;
|
||||
}
|
||||
bool integer_wire_ready() {
|
||||
if (integer_wire_checked) return integer_wire_compatible;
|
||||
Array empty,probe;
|
||||
TypedArray<int64_t> typed_empty,typed_probe;
|
||||
const int64_t boundaries[]={INT64_MIN,int64_t(INT32_MIN)-1,INT32_MIN,-1,0,1,INT32_MAX,int64_t(INT32_MAX)+1,INT64_MAX};
|
||||
for (const int64_t value: boundaries) {probe.append(value);typed_probe.append(value);}
|
||||
bool matches=true;
|
||||
for (const Array &value: {empty,probe,Array(typed_empty),Array(typed_probe)}) {
|
||||
PackedByteArray encoded;
|
||||
matches=integer_wire_encode(value,encoded) && encoded==UtilityFunctions::var_to_bytes(value) && matches;
|
||||
}
|
||||
integer_wire_compatible=matches;integer_wire_checked=true;
|
||||
return matches;
|
||||
}
|
||||
static bool integer_wire_update(const PackedByteArray &before,bool typed,int64_t old_count,
|
||||
int64_t appended,bool pop_front,PackedByteArray &after) {
|
||||
const int64_t header=typed ? 12 : 8;
|
||||
if (before.size()<header || old_count<0) return false;
|
||||
const bool remove_appended=pop_front && old_count==0;
|
||||
int64_t skipped=0;
|
||||
if (pop_front && old_count>0) {
|
||||
if (before.size()<header+8) return false;
|
||||
// Exact INT header was produced locally from the current values.
|
||||
skipped=(before[header+2] & 1) ? 12 : 8;
|
||||
if (before.size()<header+skipped) return false;
|
||||
}
|
||||
const int64_t added=remove_appended ? 0 : integer_wire_width(appended);
|
||||
const int64_t size=before.size()-skipped+added;
|
||||
if (size>INT32_MAX || after.resize(size)!=OK) return false;
|
||||
uint8_t *p=after.ptrw();const uint8_t *q=before.ptr();
|
||||
std::memcpy(p,q,size_t(header));
|
||||
wire32(p+header-4,uint32_t(old_count+1-(pop_front ? 1 : 0)));
|
||||
const int64_t kept=before.size()-header-skipped;
|
||||
if (kept>0) std::memcpy(p+header,q+header+skipped,size_t(kept));
|
||||
if (added>0) wire_integer(p+header+kept,appended);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Per-public-call immutable names. No static/global lifetime, cached
|
||||
// object values, script dispatch result or profile; recursive calls own
|
||||
// separate scopes. Destruction precedes leaving the engine call.
|
||||
struct ProjectionNames {
|
||||
const StringName k_ACCESS_LAYER_DENIED{"ACCESS_LAYER_DENIED",false};
|
||||
const StringName k_CONNECTOR{"CONNECTOR",false};
|
||||
const StringName k_GOAL_NOT_PASSABLE{"GOAL_NOT_PASSABLE",false};
|
||||
const StringName k_GOAL_TEMPORARILY_BLOCKED{"GOAL_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k_GOAL_UNREACHABLE{"GOAL_UNREACHABLE",false};
|
||||
const StringName k_OK{"OK",false};
|
||||
const StringName k_START_NOT_PASSABLE{"START_NOT_PASSABLE",false};
|
||||
const StringName k_START_TEMPORARILY_BLOCKED{"START_TEMPORARILY_BLOCKED",false};
|
||||
const StringName k__build_guest_query_tree_compact{"_build_guest_query_tree_compact",false};
|
||||
const StringName k__cache_key{"_cache_key",false};
|
||||
const StringName k__cell_traffic{"_cell_traffic",false};
|
||||
const StringName k__copy_path{"_copy_path",false};
|
||||
const StringName k__diagnose_no_route_reason{"_diagnose_no_route_reason",false};
|
||||
const StringName k__find_facility_access_path{"_find_facility_access_path",false};
|
||||
const StringName k__find_path_internal{"_find_path_internal",false};
|
||||
const StringName k__guest_access_batch_can_select{"_guest_access_batch_can_select",false};
|
||||
const StringName k__guest_batch_has_permitted_layer_crossing{"_guest_batch_has_permitted_layer_crossing",false};
|
||||
const StringName k__guest_has_checked_in{"_guest_has_checked_in",false};
|
||||
const StringName k__guest_step_connector_memo{"_guest_step_connector_memo",false};
|
||||
const StringName k__guest_tree_arithmetic_wrapped{"_guest_tree_arithmetic_wrapped",false};
|
||||
const StringName k__guest_tree_reconstruct_path{"_guest_tree_reconstruct_path",false};
|
||||
const StringName k__need_relief_for_facility_tags{"_need_relief_for_facility_tags",false};
|
||||
const StringName k__passable_cells{"_passable_cells",false};
|
||||
const StringName k__path_cache{"_path_cache",false};
|
||||
const StringName k__query_batch{"_query_batch",false};
|
||||
const StringName k__route_failure{"_route_failure",false};
|
||||
const StringName k__service_category_for_tags{"_service_category_for_tags",false};
|
||||
const StringName k__sorted_name_keys{"_sorted_name_keys",false};
|
||||
const StringName k_arithmetic_wrapped{"arithmetic_wrapped",false};
|
||||
const StringName k_available_quantity{"available_quantity",false};
|
||||
const StringName k_base_quality{"base_quality",false};
|
||||
const StringName k_base_service_minutes{"base_service_minutes",false};
|
||||
const StringName k_begin_guest_path_query_batch{"begin_guest_path_query_batch",false};
|
||||
const StringName k_cache_misses{"cache_misses",false};
|
||||
const StringName k_calculate_guest_batch_path_cost{"calculate_guest_batch_path_cost",false};
|
||||
const StringName k_candidates{"candidates",false};
|
||||
const StringName k_capacity{"capacity",false};
|
||||
const StringName k_compact{"compact",false};
|
||||
const StringName k_cost{"cost",false};
|
||||
const StringName k_cost_values{"cost_values",false};
|
||||
const StringName k_costs{"costs",false};
|
||||
const StringName k_current_cell{"current_cell",false};
|
||||
const StringName k_definition_id{"definition_id",false};
|
||||
const StringName k_definition_registry{"definition_registry",false};
|
||||
const StringName k_elevators{"elevators",false};
|
||||
const StringName k_end_guest_path_query_batch{"end_guest_path_query_batch",false};
|
||||
const StringName k_entrance{"entrance",false};
|
||||
const StringName k_estimated_total_gold{"estimated_total_gold",false};
|
||||
const StringName k_expected_quality{"expected_quality",false};
|
||||
const StringName k_expected_wait{"expected_wait",false};
|
||||
const StringName k_facility_id{"facility_id",false};
|
||||
const StringName k_facility_service{"facility_service",false};
|
||||
const StringName k_facility_tags{"facility_tags",false};
|
||||
const StringName k_get_connector{"get_connector",false};
|
||||
const StringName k_get_facility{"get_facility",false};
|
||||
const StringName k_get_facility_business_price{"get_facility_business_price",false};
|
||||
const StringName k_get_guest{"get_guest",false};
|
||||
const StringName k_get_instances{"get_instances",false};
|
||||
const StringName k_get_step_connector{"get_step_connector",false};
|
||||
const StringName k_graph_revision{"graph_revision",false};
|
||||
const StringName k_group_fit{"group_fit",false};
|
||||
const StringName k_guest_service{"guest_service",false};
|
||||
const StringName k_illegal_level{"illegal_level",false};
|
||||
const StringName k_indices{"indices",false};
|
||||
const StringName k_inputs_available{"inputs_available",false};
|
||||
const StringName k_instance_id{"instance_id",false};
|
||||
const StringName k_inventory_inputs{"inventory_inputs",false};
|
||||
const StringName k_inventory_service{"inventory_service",false};
|
||||
const StringName k_is_open{"is_open",false};
|
||||
const StringName k_last_error{"last_error",false};
|
||||
const StringName k_last_no_route_reason{"last_no_route_reason",false};
|
||||
const StringName k_last_path_cost{"last_path_cost",false};
|
||||
const StringName k_local_inventory_id{"local_inventory_id",false};
|
||||
const StringName k_movement_type{"movement_type",false};
|
||||
const StringName k_need_relief{"need_relief",false};
|
||||
const StringName k_occupied_cells{"occupied_cells",false};
|
||||
const StringName k_origin{"origin",false};
|
||||
const StringName k_owns_connector{"owns_connector",false};
|
||||
const StringName k_path{"path",false};
|
||||
const StringName k_path_handle{"path_handle",false};
|
||||
const StringName k_path_query_measured{"path_query_measured",false};
|
||||
const StringName k_path_service{"path_service",false};
|
||||
const StringName k_queue_entries{"queue_entries",false};
|
||||
const StringName k_reachable{"reachable",false};
|
||||
const StringName k_ready{"ready",false};
|
||||
const StringName k_resource_path{"resource_path",false};
|
||||
const StringName k_risk_level{"risk_level",false};
|
||||
const StringName k_service_category{"service_category",false};
|
||||
const StringName k_settled{"settled",false};
|
||||
const StringName k_staff_only{"staff_only",false};
|
||||
const StringName k_start{"start",false};
|
||||
const StringName k_state{"state",false};
|
||||
const StringName k_supported{"supported",false};
|
||||
const StringName k_tags{"tags",false};
|
||||
const StringName k_tagproj_hunger{"hunger",false};
|
||||
const StringName k_tagproj_sleep{"sleep",false};
|
||||
const StringName k_tagproj_hygiene{"hygiene",false};
|
||||
const StringName k_tagproj_medical{"medical",false};
|
||||
const StringName k_tagproj_leisure{"leisure",false};
|
||||
const StringName k_tagproj_intimacy{"intimacy",false};
|
||||
const StringName k_tagproj_dining{"dining",false};
|
||||
const StringName k_tagproj_lodging{"lodging",false};
|
||||
const StringName k_tagproj_social{"social",false};
|
||||
const StringName k_tagproj_bath{"bath",false};
|
||||
const StringName k_tagproj_wellness{"wellness",false};
|
||||
const StringName k_tagproj_adult_only{"adult_only",false};
|
||||
const StringName k_tagproj_restricted_service{"restricted_service",false};
|
||||
const StringName k_tagproj_food{"food",false};
|
||||
const StringName k_tagproj_service{"service",false};
|
||||
const StringName k_target_cell{"target_cell",false};
|
||||
const StringName k_targets{"targets",false};
|
||||
const StringName k_temporary_block{"temporary_block",false};
|
||||
const StringName k_travel_minutes{"travel_minutes",false};
|
||||
const StringName k_tree{"tree",false};
|
||||
const StringName k_usable{"usable",false};
|
||||
};
|
||||
public:
|
||||
Dictionary encode_integer_window(const Array &values) {
|
||||
Dictionary result;const StringName supported("supported",false),bytes("bytes",false);
|
||||
result[supported]=false;PackedByteArray encoded;
|
||||
if (!integer_wire_ready() || !integer_wire_encode(values,encoded)) return result;
|
||||
result[supported]=true;result[bytes]=encoded;return result;
|
||||
}
|
||||
// Whole builtin integer record operation. No retained sample values or time
|
||||
// suppression. Unsupported data returns before any caller-owned mutation.
|
||||
Dictionary performance_record_sample(Object *owner, const StringName &metric, int64_t duration) {
|
||||
const StringName supported("supported",false), report_key("report",false);
|
||||
Dictionary unsupported; unsupported[supported]=false;
|
||||
Object *script=owner ? owner->get_script() : nullptr;
|
||||
if (!script || String(script->get(StringName("resource_path",false))) != "res://scripts/core/performance_budget.gd" || metric==StringName() || duration<0) return unsupported;
|
||||
const StringName samples_key("_samples",false), ordered_key("_ordered_samples",false), bytes_key("_ordered_sample_bytes",false), integer_key("_integer_window_bytes",false), totals_key("_totals",false), counts_key("_sample_counts",false), budgets_key("_budgets",false);
|
||||
const Variant sv=owner->get(samples_key), ov=owner->get(ordered_key), bv=owner->get(bytes_key), iv=owner->get(integer_key), tv=owner->get(totals_key), cv=owner->get(counts_key), uv=owner->get(budgets_key), capacityv=owner->get(StringName("sample_capacity",false));
|
||||
if (sv.get_type()!=Variant::DICTIONARY || ov.get_type()!=Variant::DICTIONARY || bv.get_type()!=Variant::DICTIONARY || iv.get_type()!=Variant::DICTIONARY || tv.get_type()!=Variant::DICTIONARY || cv.get_type()!=Variant::DICTIONARY || uv.get_type()!=Variant::DICTIONARY || capacityv.get_type()!=Variant::INT) return unsupported;
|
||||
Dictionary samples=sv, orders=ov, rawbytes=bv, integers=iv, totals=tv, counts=cv, budgets=uv;
|
||||
if (samples.is_read_only() || orders.is_read_only() || rawbytes.is_read_only() || integers.is_read_only() || totals.is_read_only() || counts.is_read_only()) return unsupported;
|
||||
const std::vector<Dictionary> maps{samples,orders,rawbytes,integers,totals,counts,budgets};
|
||||
for (size_t i=0;i<maps.size();++i) for (size_t j=i+1;j<maps.size();++j)
|
||||
if (UtilityFunctions::is_same(maps[i],maps[j])) return unsupported;
|
||||
const bool existing=samples.has(metric);
|
||||
Variant rawv=samples.get(metric,Array());
|
||||
if (rawv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array values=rawv;
|
||||
if (values.is_read_only()) return unsupported;
|
||||
|
||||
Variant totalv=totals.get(metric,0), countv=counts.get(metric,0), budgetv=budgets.get(metric,Dictionary());
|
||||
if (totalv.get_type()!=Variant::INT || countv.get_type()!=Variant::INT || budgetv.get_type()!=Variant::DICTIONARY) return unsupported;
|
||||
Dictionary budget=budgetv;
|
||||
for (const Dictionary &map: maps) if (UtilityFunctions::is_same(budget,map)) return unsupported;
|
||||
const StringName aggregation_key("aggregation",false), limit_key("limit_us",false);
|
||||
Variant aggregationv=budget.get(aggregation_key,1), limitv=budget.get(limit_key,0);
|
||||
if (aggregationv.get_type()!=Variant::INT || limitv.get_type()!=Variant::INT) return unsupported;
|
||||
// One exact byte snapshot serves both builtin validation helpers inside
|
||||
// this call; there are no external calls between their original checks.
|
||||
PackedByteArray before;
|
||||
const bool fast_wire=integer_wire_ready() && integer_wire_encode(values,before);
|
||||
if (!fast_wire) {
|
||||
for (int64_t i=0;i<values.size();++i) if (values[i].get_type()!=Variant::INT) return unsupported;
|
||||
before=UtilityFunctions::var_to_bytes(values);
|
||||
}
|
||||
const int64_t old_count=values.size();
|
||||
TypedArray<int64_t> ordered;
|
||||
Variant savedbytes=rawbytes.get(metric,PackedByteArray());
|
||||
bool reuse=existing && orders.has(metric) && savedbytes.get_type()==Variant::PACKED_BYTE_ARRAY && PackedByteArray(savedbytes)==before;
|
||||
if (reuse) {
|
||||
Variant orderv=orders[metric];
|
||||
if (orderv.get_type()!=Variant::ARRAY) return unsupported;
|
||||
Array prior=orderv;
|
||||
if (prior.is_read_only() || !prior.is_typed() || prior.get_typed_builtin()!=Variant::INT || prior.size()!=values.size()) return unsupported;
|
||||
ordered=prior;
|
||||
} else {
|
||||
for (int64_t i=0;i<values.size();++i) ordered.append(values[i]);
|
||||
ordered.sort();
|
||||
}
|
||||
// Guards completed: mutations preserve raw and reused ordered aliases.
|
||||
if (!existing) samples[metric]=values;
|
||||
integers[metric]=before;
|
||||
if (!reuse) { orders[metric]=ordered; rawbytes[metric]=before; }
|
||||
values.append(duration);
|
||||
ordered.insert(ordered.bsearch(duration),duration);
|
||||
if (values.size()>int64_t(capacityv)) {
|
||||
Variant removed=values.pop_front();
|
||||
ordered.remove_at(ordered.bsearch(removed));
|
||||
}
|
||||
PackedByteArray after;
|
||||
const bool popped=old_count+1>int64_t(capacityv);
|
||||
if (!fast_wire || !integer_wire_update(before,values.is_typed(),old_count,duration,popped,after))
|
||||
after=UtilityFunctions::var_to_bytes(values);
|
||||
rawbytes[metric]=after; integers[metric]=after;
|
||||
const int64_t lifetime_total=the_tree_native::signed_add(int64_t(totalv),duration);
|
||||
const int64_t lifetime_count=the_tree_native::signed_add(int64_t(countv),1);
|
||||
totals[metric]=lifetime_total; counts[metric]=lifetime_count;
|
||||
const int64_t average=lifetime_count>0 ? lifetime_total/lifetime_count : 0;
|
||||
const int64_t maximum=ordered.is_empty() ? 0 : std::max<int64_t>(0,int64_t(ordered[ordered.size()-1]));
|
||||
int64_t p95=0;
|
||||
if (!ordered.is_empty()) {
|
||||
const int64_t index=std::max<int64_t>(the_tree_native::signed_add(the_tree_native::signed_add(signed_product(95,ordered.size()),99)/100,-1),0);
|
||||
p95=ordered[std::min<int64_t>(index,ordered.size()-1)];
|
||||
}
|
||||
const int64_t aggregation=aggregationv, limit=limitv;
|
||||
// Original default match is P95, including unknown aggregation values.
|
||||
const int64_t measured=aggregation==0 ? average : (aggregation==2 ? maximum : p95);
|
||||
Dictionary report;
|
||||
report[StringName("metric_id",false)]=metric;
|
||||
report[StringName("window_sample_count",false)]=values.size();
|
||||
report[StringName("lifetime_sample_count",false)]=lifetime_count;
|
||||
report[StringName("average_us",false)]=average;
|
||||
report[StringName("p95_us",false)]=p95;
|
||||
report[StringName("maximum_us",false)]=maximum;
|
||||
report[aggregation_key]=aggregation;
|
||||
report[StringName("measured_us",false)]=measured;
|
||||
report[limit_key]=limit;
|
||||
report[StringName("budget_exceeded",false)]=limit>0 && measured>limit;
|
||||
Dictionary result; result[supported]=true; result[report_key]=report; return result;
|
||||
}
|
||||
void clear_edge_cache() { cached_edges.clear(); }
|
||||
Dictionary search(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, false);
|
||||
}
|
||||
Dictionary search_cached(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider) {
|
||||
return search_impl(cells, indices, start_index, goals, edge_provider, true);
|
||||
}
|
||||
// One ordered projection, with no new persistent ownership/cache. Only
|
||||
// builtin pure getters and the already guarded sole budget observer qualify.
|
||||
Dictionary project_guest_candidates(Object *runtime, const StringName &guest_id,
|
||||
int64_t open_state, int64_t degraded_state, int64_t movement_scale,
|
||||
int64_t stair_credit, int64_t orthogonal_cost, const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!builtin(n,runtime, "res://autoload/simulation_runtime.gd") || !ticks.is_valid()) return unsupported;
|
||||
Object *path = runtime->get(n.k_path_service);
|
||||
Object *elevators = runtime->get(n.k_elevators);
|
||||
Object *facilities = runtime->get(n.k_facility_service);
|
||||
Object *definitions = runtime->get(n.k_definition_registry);
|
||||
Object *inventory = runtime->get(n.k_inventory_service);
|
||||
Object *guests = runtime->get(n.k_guest_service);
|
||||
if (!builtin(n,path,"res://scripts/world/path_service.gd") ||
|
||||
!builtin(n,elevators,"res://scripts/transport/elevator_service.gd") ||
|
||||
!builtin(n,facilities,"res://scripts/facility/facility_service.gd") ||
|
||||
!builtin(n,definitions,"res://scripts/core/definition_registry.gd") ||
|
||||
!builtin(n,inventory,"res://scripts/inventory/inventory_service.gd") ||
|
||||
!builtin(n,guests,"res://scripts/guest/guest_service.gd") ||
|
||||
!static_cast<bool>(runtime->call(n.k__guest_access_batch_can_select))) return unsupported;
|
||||
Object *guest = guests->call(n.k_get_guest,guest_id);
|
||||
Array candidates;
|
||||
if (!guest) {
|
||||
Dictionary result; result[n.k_supported] = true;
|
||||
result[n.k_candidates] = candidates; return result;
|
||||
}
|
||||
if (!builtin(n,guest,"res://scripts/guest/guest_runtime.gd")) return unsupported;
|
||||
const Array initial = facilities->call(n.k_get_instances);
|
||||
// Preflight is read-only: default registry/get_instances have no public
|
||||
// error/counter effects. Custom resources retain the original whole loop.
|
||||
for (int64_t i=0;i<initial.size();++i) {
|
||||
Object *facility = initial[i];
|
||||
if (!builtin(n,facility,"res://scripts/facility/facility_instance.gd")) return unsupported;
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (definition && !builtin(n,definition,"res://scripts/data/facility_definition.gd")) return unsupported;
|
||||
}
|
||||
Array goals;
|
||||
for (int64_t i=0;i<initial.size();++i) goals.append_array(facility_access_cells(n,path,initial[i]));
|
||||
const Vector3i start = guest->get(n.k_current_cell);
|
||||
path->call(n.k_begin_guest_path_query_batch,start,goals);
|
||||
const Array ordered = facilities->call(n.k_get_instances);
|
||||
TypedArray<int64_t> layers; layers.push_back(0); layers.push_back(1);
|
||||
TypedArray<StringName> access_ids;
|
||||
for (int64_t i=0;i<ordered.size();++i) {
|
||||
Object *facility = ordered[i];
|
||||
Object *definition = definitions->call(n.k_get_facility,facility->get(n.k_definition_id));
|
||||
if (!definition) continue;
|
||||
const Array tags = definition->get(n.k_tags);
|
||||
if (tags.has(n.k_staff_only)) continue;
|
||||
if (tags.has(n.k_entrance) && static_cast<bool>(runtime->call(n.k__guest_has_checked_in,guest))) continue;
|
||||
bool has_inputs = true;
|
||||
const Dictionary inputs = definition->get(n.k_inventory_inputs);
|
||||
const Array items = runtime->call(n.k__sorted_name_keys,inputs);
|
||||
for (int64_t j=0;j<items.size();++j) {
|
||||
const int64_t quantity = inventory->call(n.k_available_quantity,facility->get(n.k_local_inventory_id),items[j]);
|
||||
if (quantity < static_cast<int64_t>(inputs[items[j]])) {has_inputs=false;break;}
|
||||
}
|
||||
if (!has_inputs) {
|
||||
Dictionary row;row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_inputs_available]=false;candidates.push_back(row);continue;
|
||||
}
|
||||
const Array destinations = facility_access_cells(n,path,facility);
|
||||
Dictionary route = nearest_guest_access_impl(n,path,start,destinations,facility->get(n.k_origin),layers,access_ids,ticks);
|
||||
if (static_cast<bool>(route.get(n.k_supported,false))) route.erase(n.k_supported);
|
||||
else route = Dictionary(runtime->call(n.k__find_facility_access_path,start,facility));
|
||||
const Array walk = route.get(n.k_path,Array());
|
||||
const bool plain_tag_relief = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
const Dictionary relief = plain_tag_relief ? builtin_tag_relief(n,tags) :
|
||||
Dictionary(runtime->call(n.k__need_relief_for_facility_tags,tags));
|
||||
Dictionary row;
|
||||
row[n.k_facility_id]=facility->get(n.k_instance_id);
|
||||
row[n.k_facility_tags]=tags.duplicate();
|
||||
const int64_t state = facility->get(n.k_state);
|
||||
row[n.k_is_open]=(state==open_state || state==degraded_state);
|
||||
row[n.k_reachable]=route.get(n.k_reachable,false);
|
||||
row[n.k_travel_minutes]=guest_path_minutes(n,path,elevators,walk,movement_scale,stair_credit,orthogonal_cost);
|
||||
const Array queue = facility->get(n.k_queue_entries);
|
||||
const int64_t capacity = facility->get(n.k_capacity);
|
||||
row[n.k_expected_wait]=signed_product(queue.size(),facility->get(n.k_base_service_minutes))/std::max<int64_t>(capacity,1);
|
||||
row[n.k_estimated_total_gold]=runtime->call(n.k_get_facility_business_price,facility);
|
||||
const int64_t quality = facility->get(n.k_base_quality);
|
||||
row[n.k_expected_quality]=std::max<int64_t>(0,std::min<int64_t>(quality,100));
|
||||
row[n.k_need_relief]=relief;
|
||||
row[n.k_risk_level]=int64_t(0);row[n.k_illegal_level]=int64_t(0);
|
||||
row[n.k_group_fit]=signed_product(std::min<int64_t>(capacity,4),5);
|
||||
// Recheck at the original call position: preceding price/observer
|
||||
// callbacks must retain a replaced/custom runtime's original call.
|
||||
const bool plain_tag_category = builtin(n,runtime,"res://autoload/simulation_runtime.gd") &&
|
||||
tags.is_typed() && tags.get_typed_builtin()==Variant::STRING_NAME;
|
||||
row[n.k_service_category]=plain_tag_category ? Variant(builtin_tag_category(n,tags)) :
|
||||
runtime->call(n.k__service_category_for_tags,tags);
|
||||
Dictionary handle;handle[n.k_target_cell]=route.get(n.k_target_cell,guest->get(n.k_current_cell));
|
||||
handle[n.k_graph_revision]=path->get(n.k_graph_revision);
|
||||
row[n.k_path_handle]=handle;candidates.push_back(row);
|
||||
}
|
||||
path->call(n.k_end_guest_path_query_batch);
|
||||
Dictionary result;result[n.k_supported]=true;result[n.k_candidates]=candidates;return result;
|
||||
}
|
||||
private:
|
||||
// Direct translation of the two pure builtin tag methods. Fresh result,
|
||||
// original insertion/branch order, no retained definition/tag memo.
|
||||
static Dictionary builtin_tag_relief(const ProjectionNames &n,const Array &tags) {
|
||||
Dictionary relief;
|
||||
relief[n.k_tagproj_hunger]=int64_t(0);relief[n.k_tagproj_sleep]=int64_t(0);
|
||||
relief[n.k_tagproj_hygiene]=int64_t(0);relief[n.k_tagproj_medical]=int64_t(0);
|
||||
relief[n.k_tagproj_leisure]=int64_t(0);relief[n.k_tagproj_intimacy]=int64_t(0);
|
||||
if(tags.has(n.k_tagproj_dining)) {relief[n.k_tagproj_hunger]=int64_t(80);relief[n.k_tagproj_leisure]=int64_t(10);}
|
||||
if(tags.has(n.k_tagproj_lodging)) {relief[n.k_tagproj_sleep]=int64_t(90);relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),10);}
|
||||
if(tags.has(n.k_tagproj_social)||tags.has(n.k_tagproj_leisure)) relief[n.k_tagproj_leisure]=std::max<int64_t>(int64_t(relief[n.k_tagproj_leisure]),40);
|
||||
if(tags.has(n.k_tagproj_hygiene)||tags.has(n.k_tagproj_bath)) relief[n.k_tagproj_hygiene]=std::max<int64_t>(int64_t(relief[n.k_tagproj_hygiene]),80);
|
||||
if(tags.has(n.k_tagproj_medical)) relief[n.k_tagproj_medical]=int64_t(90);
|
||||
else if(tags.has(n.k_tagproj_wellness)) relief[n.k_tagproj_medical]=std::max<int64_t>(int64_t(relief[n.k_tagproj_medical]),20);
|
||||
if(tags.has(n.k_tagproj_adult_only)||tags.has(n.k_tagproj_restricted_service)) relief[n.k_tagproj_intimacy]=int64_t(80);
|
||||
return relief;
|
||||
}
|
||||
static StringName builtin_tag_category(const ProjectionNames &n,const Array &tags) {
|
||||
if(tags.has(n.k_tagproj_dining))return n.k_tagproj_food;
|
||||
if(tags.has(n.k_tagproj_lodging))return n.k_tagproj_lodging;
|
||||
if(tags.has(n.k_tagproj_social))return n.k_tagproj_leisure;
|
||||
return n.k_tagproj_service;
|
||||
}
|
||||
static bool builtin(const ProjectionNames &n,Object *object,const char *resource) {
|
||||
if (!object) return false;Object *script=object->get_script();
|
||||
return script && String(script->get(n.k_resource_path))==resource;
|
||||
}
|
||||
// Exact builtin cache-key spelling. No normalized/reordered profile and no
|
||||
// persistent cache; String lengths count Unicode codepoints as GDScript does.
|
||||
static String cache_key_suffix(const ProjectionNames &n,Object *path, const TypedArray<int64_t> &layers,
|
||||
const TypedArray<StringName> &access) {
|
||||
String layer_text, access_text;
|
||||
for (int64_t i=0; i<layers.size(); ++i) {
|
||||
if (i) layer_text += ",";
|
||||
layer_text += String::num_int64(static_cast<int64_t>(layers[i]));
|
||||
}
|
||||
for (int64_t i=0; i<access.size(); ++i) {
|
||||
if (i) access_text += ",";
|
||||
const String value = String(static_cast<StringName>(access[i]));
|
||||
access_text += String::num_int64(value.length()) + ":" + value;
|
||||
}
|
||||
return String("|L:") + layer_text + "|A:" + access_text + "|R:" +
|
||||
String::num_int64(static_cast<int64_t>(path->get(n.k_graph_revision)));
|
||||
}
|
||||
static String cache_key_with_suffix(const Vector3i &start, const Vector3i &goal,
|
||||
const String &suffix) {
|
||||
return String::num_int64(start.x) + "," + String::num_int64(start.y) + "," +
|
||||
String::num_int64(start.z) + ">" + String::num_int64(goal.x) + "," +
|
||||
String::num_int64(goal.y) + "," + String::num_int64(goal.z) + suffix;
|
||||
}
|
||||
static int64_t signed_product(int64_t a,int64_t b) {
|
||||
const uint64_t product=static_cast<uint64_t>(a)*static_cast<uint64_t>(b);
|
||||
if (product<=static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) return static_cast<int64_t>(product);
|
||||
return std::numeric_limits<int64_t>::min()+static_cast<int64_t>(product-(uint64_t(1)<<63));
|
||||
}
|
||||
static Array facility_access_cells(const ProjectionNames &n,Object *path,Object *facility) {
|
||||
const Dictionary passable=path->get(n.k__passable_cells);
|
||||
Array occupied=Array(facility->get(n.k_occupied_cells)).duplicate();
|
||||
if (occupied.is_empty()) occupied.push_back(facility->get(n.k_origin));
|
||||
Dictionary unique;
|
||||
for (int64_t i=0;i<occupied.size();++i) {
|
||||
const Vector3i cell=occupied[i];
|
||||
for (int32_t dx : {-1,1}) {
|
||||
// Match GDScript's Vector3i conversion after int64 addition.
|
||||
const Vector3i candidate(int64_t(cell.x)+dx,cell.y,cell.z);
|
||||
if (passable.has(candidate)) unique[candidate]=true;
|
||||
}
|
||||
}
|
||||
const Vector3i origin=facility->get(n.k_origin);if(passable.has(origin)) unique[origin]=true;
|
||||
const Array keys=unique.keys();std::vector<Vector3i> sorted;sorted.reserve(keys.size());
|
||||
for (int64_t i=0;i<keys.size();++i) sorted.push_back(keys[i]);
|
||||
std::sort(sorted.begin(),sorted.end(),[](const Vector3i &a,const Vector3i &b){
|
||||
if(a.z!=b.z)return a.z<b.z;if(a.y!=b.y)return a.y<b.y;return a.x<b.x;
|
||||
});
|
||||
Array result;for(const Vector3i &cell:sorted)result.push_back(cell);return result;
|
||||
}
|
||||
static int64_t guest_path_minutes(const ProjectionNames &n,Object *path,Object *elevators,const Array &route,
|
||||
int64_t movement_scale,int64_t stair_credit,int64_t orthogonal_cost) {
|
||||
Dictionary memo=path->get(n.k__guest_step_connector_memo);
|
||||
// Route-local only. Reuse an ownership result only after an actual
|
||||
// BOOL return and a typed cached step; no script callback occurs on a
|
||||
// repeated typed memo hit. A miss/conversion/custom script keeps calls.
|
||||
const bool builtin_pair = builtin(n,path,"res://scripts/world/path_service.gd") &&
|
||||
builtin(n,elevators,"res://scripts/transport/elevator_service.gd");
|
||||
bool ownership_valid = false;
|
||||
bool ownership_value = false;
|
||||
StringName ownership_connector;
|
||||
for(int64_t i=1;i<route.size();++i) {
|
||||
const Vector3i from=route[i-1],to=route[i];
|
||||
const Dictionary destinations=memo.get(from,Dictionary());
|
||||
StringName connector;
|
||||
const bool memo_hit = destinations.has(to);
|
||||
const bool typed_memo_hit = memo_hit && destinations[to].get_type() == Variant::STRING_NAME;
|
||||
if(memo_hit)connector=destinations[to];
|
||||
else {TypedArray<StringName> access;connector=path->call(n.k_get_step_connector,from,to,access);}
|
||||
bool special;
|
||||
if (builtin_pair && typed_memo_hit && ownership_valid && connector == ownership_connector) {
|
||||
special = ownership_value;
|
||||
} else {
|
||||
const Variant owned = elevators->call(n.k_owns_connector,connector);
|
||||
special = static_cast<bool>(owned);
|
||||
ownership_valid = builtin_pair && typed_memo_hit && owned.get_type() == Variant::BOOL;
|
||||
ownership_value = special;
|
||||
ownership_connector = connector;
|
||||
}
|
||||
if(!special && String(connector).begins_with("infra.connector:") && from.x==to.x && from.z==to.z &&
|
||||
std::abs(int64_t(from.y)-int64_t(to.y))==1) {
|
||||
const Dictionary detail=path->call(n.k_get_connector,connector);
|
||||
special=detail.get(n.k_movement_type,StringName())==Variant(n.k_CONNECTOR) &&
|
||||
static_cast<int64_t>(detail.get(n.k_cost,int64_t(0)))==150 && stair_credit>movement_scale;
|
||||
}
|
||||
if(special) {
|
||||
const int64_t cost=path->call(n.k_calculate_guest_batch_path_cost,route[0],route[route.size()-1],route);
|
||||
return static_cast<int64_t>(std::ceil(static_cast<double>(cost)/static_cast<double>(orthogonal_cost)));
|
||||
}
|
||||
}
|
||||
return std::max<int64_t>(route.size()-1,0);
|
||||
}
|
||||
public:
|
||||
|
||||
// Stateless translation of the builtin nearest access query. The caller
|
||||
// supplies the existing typed default profile and the real Time callable.
|
||||
// Public cache publication, failure diagnostics and fallback stay ordered.
|
||||
Dictionary nearest_guest_access(Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
const ProjectionNames n;
|
||||
return nearest_guest_access_impl(n,path,start,goals,fallback_target,layers,access,ticks);
|
||||
}
|
||||
private:
|
||||
Dictionary nearest_guest_access_impl(const ProjectionNames &n,Object *path, const Vector3i &start,
|
||||
const Array &goals, const Vector3i &fallback_target,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access,
|
||||
const Callable &ticks) {
|
||||
Dictionary unsupported;
|
||||
unsupported[n.k_supported] = false;
|
||||
if (!path || !ticks.is_valid() || goals.is_empty()) return unsupported;
|
||||
Object *script = path->get_script();
|
||||
if (!script || String(script->get(n.k_resource_path)) != "res://scripts/world/path_service.gd") return unsupported;
|
||||
Dictionary batch = path->get(n.k__query_batch);
|
||||
if (batch.is_empty() || start != static_cast<Vector3i>(batch[n.k_start])) return unsupported;
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start) || blocked(n,path, start) || !layers.has(start.z)) return unsupported;
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
// Exact builtin validation loop has no callback before ticks.call().
|
||||
// Build the unchanged profile/revision suffix lazily at the first key.
|
||||
String key_suffix;
|
||||
bool key_suffix_ready = false;
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
if (goals[j].get_type() != Variant::VECTOR3I) return unsupported;
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell)) {
|
||||
const bool searchable = passable.has(cell) && layers.has(cell.z) && !blocked(n,path, cell);
|
||||
if (searchable && (start.z == cell.z || static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, cell, layers, access)))) return unsupported;
|
||||
}
|
||||
if (!key_suffix_ready) {
|
||||
key_suffix = cache_key_suffix(n,path, layers, access);
|
||||
key_suffix_ready = true;
|
||||
}
|
||||
const String key = cache_key_with_suffix(start, cell, key_suffix);
|
||||
if (cache.has(key)) return unsupported;
|
||||
}
|
||||
const int64_t started = ticks.call();
|
||||
if (!static_cast<bool>(batch[n.k_ready])) {
|
||||
batch[n.k_tree] = path->call(n.k__build_guest_query_tree_compact, start, targets, layers, access);
|
||||
batch[n.k_arithmetic_wrapped] = path->get(n.k__guest_tree_arithmetic_wrapped);
|
||||
batch[n.k_ready] = true;
|
||||
}
|
||||
const Dictionary tree = batch[n.k_tree];
|
||||
if (!static_cast<bool>(tree.get(n.k_usable, false)) || static_cast<bool>(batch.get(n.k_arithmetic_wrapped, false))) return unsupported;
|
||||
Vector3i best_cell = fallback_target;
|
||||
bool found = false;
|
||||
int64_t best_cost = 0;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
for (int64_t j = 0; j < goals.size(); ++j) {
|
||||
const Vector3i cell = goals[j];
|
||||
if (!targets.has(cell) || !settled.has(cell)) continue;
|
||||
if (!tree_has_cost(n,tree, cell)) return unsupported;
|
||||
const int64_t cost = tree_cost(n,tree, cell);
|
||||
if (!found || cost < best_cost) {
|
||||
found = true;
|
||||
best_cost = cost;
|
||||
best_cell = cell;
|
||||
}
|
||||
}
|
||||
TypedArray<Vector3i> best_path;
|
||||
if (found) {
|
||||
best_path = resolve_ready_guest_point(n,path, start, best_cell, tree, layers, access);
|
||||
if (best_path.is_empty()) return unsupported;
|
||||
}
|
||||
if (!goals.is_empty() && (!found || goals[goals.size() - 1] != Variant(best_cell))) {
|
||||
resolve_ready_guest_point(n,path, start, goals[goals.size() - 1], tree, layers, access);
|
||||
}
|
||||
const int64_t elapsed = the_tree_native::signed_add(static_cast<int64_t>(ticks.call()), the_tree_native::signed_add(~started, 1));
|
||||
path->emit_signal(n.k_path_query_measured, std::max<int64_t>(elapsed, 0));
|
||||
Dictionary result;
|
||||
result[n.k_supported] = true;
|
||||
result[n.k_reachable] = found;
|
||||
result[n.k_path] = best_path;
|
||||
result[n.k_target_cell] = best_cell;
|
||||
return result;
|
||||
}
|
||||
private:
|
||||
static bool blocked(const ProjectionNames &n,Object *path, const Vector3i &cell) {
|
||||
const Dictionary traffic_map = path->get(n.k__cell_traffic);
|
||||
const Dictionary traffic = traffic_map.get(cell, Dictionary());
|
||||
return !traffic.is_empty() && static_cast<bool>(traffic[n.k_temporary_block]);
|
||||
}
|
||||
static bool tree_has_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return values.has(cell);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
if (!indices.has(cell)) return false;
|
||||
const int64_t index = indices[cell];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return index >= 0 && index < values.size();
|
||||
}
|
||||
static int64_t tree_cost(const ProjectionNames &n,const Dictionary &tree, const Vector3i &cell) {
|
||||
if (!static_cast<bool>(tree.get(n.k_compact, false))) {
|
||||
const Dictionary values = tree[n.k_costs];
|
||||
return static_cast<int64_t>(values[cell]);
|
||||
}
|
||||
const Dictionary indices = tree[n.k_indices];
|
||||
const PackedInt64Array values = tree[n.k_cost_values];
|
||||
return values[static_cast<int64_t>(indices[cell])];
|
||||
}
|
||||
static TypedArray<Vector3i> resolve_ready_guest_point(const ProjectionNames &n,Object *path,
|
||||
const Vector3i &start, const Vector3i &goal, const Dictionary &tree,
|
||||
const TypedArray<int64_t> &layers, const TypedArray<StringName> &access) {
|
||||
const Dictionary passable = path->get(n.k__passable_cells);
|
||||
if (!passable.has(start)) return path->call(n.k__route_failure, n.k_START_NOT_PASSABLE);
|
||||
if (!passable.has(goal)) return path->call(n.k__route_failure, n.k_GOAL_NOT_PASSABLE);
|
||||
if (!layers.has(start.z) || !layers.has(goal.z)) return path->call(n.k__route_failure, n.k_ACCESS_LAYER_DENIED);
|
||||
if (blocked(n,path, start)) return path->call(n.k__route_failure, n.k_START_TEMPORARILY_BLOCKED);
|
||||
if (blocked(n,path, goal)) return path->call(n.k__route_failure, n.k_GOAL_TEMPORARILY_BLOCKED);
|
||||
// ticks/tree callbacks may have changed the script or revision. Recheck
|
||||
// at this original call position and never reuse the pre-tick suffix.
|
||||
const String key = builtin(n,path,"res://scripts/world/path_service.gd") ?
|
||||
cache_key_with_suffix(start, goal, cache_key_suffix(n,path, layers, access)) :
|
||||
String(path->call(n.k__cache_key, start, goal, layers, access));
|
||||
Dictionary cache = path->get(n.k__path_cache);
|
||||
const Dictionary batch = path->get(n.k__query_batch);
|
||||
const Dictionary targets = batch[n.k_targets];
|
||||
if (cache.has(key) || !targets.has(goal)) return path->call(n.k__find_path_internal, start, goal);
|
||||
path->set(n.k_cache_misses, the_tree_native::signed_add(static_cast<int64_t>(path->get(n.k_cache_misses)), 1));
|
||||
if (start.z != goal.z && !static_cast<bool>(path->call(n.k__guest_batch_has_permitted_layer_crossing, start, goal, layers, access))) return path->call(n.k__route_failure, n.k_GOAL_UNREACHABLE);
|
||||
TypedArray<Vector3i> route;
|
||||
const Dictionary settled = tree[n.k_settled];
|
||||
if (start == goal) route.push_back(start);
|
||||
else if (settled.has(goal)) route = Array(path->call(n.k__guest_tree_reconstruct_path, tree, goal));
|
||||
if (route.is_empty()) return path->call(n.k__route_failure, path->call(n.k__diagnose_no_route_reason, start, goal, layers, access));
|
||||
cache[key] = path->call(n.k__copy_path, route);
|
||||
path->set(n.k_last_no_route_reason, n.k_OK);
|
||||
path->set(n.k_last_path_cost, tree_cost(n,tree, goal));
|
||||
path->set(n.k_last_error, String(""));
|
||||
return route;
|
||||
}
|
||||
public:
|
||||
private:
|
||||
// One existing builtin provider operation, with no new value cache. Only
|
||||
// connector-free WALK rows qualify; unsupported cases call the old provider.
|
||||
// All work stays private until the original final shared[current] assignment.
|
||||
struct WalkEdgeNames {
|
||||
const StringName resource_path{"resource_path",false}, constants_method{"get_script_constant_map",false};
|
||||
const StringName front{"FRONT",false}, back{"BACK",false}, walk{"MOVEMENT_WALK",false};
|
||||
const StringName base{"ORTHOGONAL_MOVE_COST",false}, congestion{"CONGESTION_COST_PER_EXCESS",false};
|
||||
const StringName workspace{"_guest_tree_workspace",false}, edges{"edges",false}, indices{"indices",false};
|
||||
const StringName adjacency{"_adjacency",false}, traffic{"_cell_traffic",false}, vertical{"_vertical_walk_requires_connector",false};
|
||||
const StringName to{"to",false}, connector{"connector_id",false}, access{"access_id",false}, movement{"movement_type",false};
|
||||
const StringName base_cost{"base_cost",false}, cost{"cost",false}, temporary{"temporary_block",false};
|
||||
const StringName dynamic{"dynamic_cost",false}, density{"current_density",false}, capacity{"base_capacity",false};
|
||||
const StringName provider{"_guest_tree_shared_edges",false};
|
||||
};
|
||||
struct WalkEdgeContext {
|
||||
Object *path=nullptr;
|
||||
int64_t front=0, back=0, base=0, congestion=0;
|
||||
StringName walk;
|
||||
};
|
||||
static bool walk_edge_context(const WalkEdgeNames &n,Object *path,const Array &layers,
|
||||
const Array &access,WalkEdgeContext &context) {
|
||||
if (!path || layers.size()!=2 || !access.is_empty() ||
|
||||
layers[0].get_type()!=Variant::INT || layers[1].get_type()!=Variant::INT) return false;
|
||||
Object *script=path->get_script();
|
||||
if (!script || String(script->get(n.resource_path))!="res://scripts/world/path_service.gd" ||
|
||||
!script->has_method(n.constants_method)) return false;
|
||||
const Variant value=script->call(n.constants_method);
|
||||
if (value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary constants=value;
|
||||
for (const StringName &key : {n.front,n.back,n.base,n.congestion})
|
||||
if (constants.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
if (constants.get(n.walk,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
int64_t(layers[0])!=int64_t(constants[n.front]) ||
|
||||
int64_t(layers[1])!=int64_t(constants[n.back])) return false;
|
||||
context.path=path;context.front=constants[n.front];context.back=constants[n.back];
|
||||
context.base=constants[n.base];context.congestion=constants[n.congestion];context.walk=constants[n.walk];
|
||||
return true;
|
||||
}
|
||||
static bool builtin_walk_edge_rows(const WalkEdgeNames &n,const WalkEdgeContext &context,
|
||||
const Vector3i ¤t,Array &rows) {
|
||||
if (!context.path) return false;
|
||||
const Variant workspace_value=context.path->get(n.workspace);
|
||||
if (workspace_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary workspace=workspace_value;
|
||||
const Variant shared_value=workspace.get(n.edges,Variant());
|
||||
if (shared_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
Dictionary shared=shared_value;
|
||||
// Original cached-row branch precedes readonly and all other state reads.
|
||||
if (shared.has(current)) {
|
||||
const Variant cached=shared[current];
|
||||
if (cached.get_type()!=Variant::ARRAY) return false;
|
||||
rows=cached;return true;
|
||||
}
|
||||
if (shared.is_read_only()) return false;
|
||||
const Variant indices_value=workspace.get(n.indices,Variant()), adjacency_value=context.path->get(n.adjacency);
|
||||
const Variant traffic_value=context.path->get(n.traffic), vertical_value=context.path->get(n.vertical);
|
||||
if (indices_value.get_type()!=Variant::DICTIONARY || adjacency_value.get_type()!=Variant::DICTIONARY ||
|
||||
traffic_value.get_type()!=Variant::DICTIONARY || vertical_value.get_type()!=Variant::BOOL) return false;
|
||||
const Dictionary indices=indices_value, adjacency=adjacency_value, traffic=traffic_value;
|
||||
const Variant raw_value=adjacency.get(current,Array());
|
||||
if (raw_value.get_type()!=Variant::ARRAY) return false;
|
||||
const Array raw=raw_value;Array result;
|
||||
for (int64_t i=0;i<raw.size();++i) {
|
||||
if (raw[i].get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary edge=raw[i];
|
||||
if (edge.get(n.to,Variant()).get_type()!=Variant::VECTOR3I) return false;
|
||||
const Vector3i neighbor=edge[n.to];
|
||||
if (neighbor.z!=context.front && neighbor.z!=context.back) continue;
|
||||
const Variant row_value=traffic.get(neighbor,Dictionary());
|
||||
if (row_value.get_type()!=Variant::DICTIONARY) return false;
|
||||
const Dictionary row=row_value;
|
||||
if (!row.is_empty()) {
|
||||
if (row.get(n.temporary,Variant()).get_type()!=Variant::BOOL) return false;
|
||||
if (bool(row[n.temporary])) continue;
|
||||
}
|
||||
// Preserve old method for every connector/access/other movement row.
|
||||
if (edge.get(n.connector,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.access,Variant()).get_type()!=Variant::STRING_NAME ||
|
||||
edge.get(n.movement,Variant()).get_type()!=Variant::STRING_NAME) return false;
|
||||
const StringName connector=edge[n.connector], access=edge[n.access], movement=edge[n.movement];
|
||||
if (!connector.is_empty() || !access.is_empty() || movement!=context.walk) return false;
|
||||
if (bool(vertical_value) && current.y!=neighbor.y) continue;
|
||||
const Variant base=edge.get(n.base_cost,edge.get(n.cost,context.base));
|
||||
if (base.get_type()!=Variant::INT) return false;
|
||||
int64_t dynamic=0;
|
||||
if (!row.is_empty()) {
|
||||
for (const StringName &key : {n.dynamic,n.density,n.capacity})
|
||||
if (row.get(key,Variant()).get_type()!=Variant::INT) return false;
|
||||
const int64_t excess=the_tree_native::signed_add(int64_t(row[n.density]),
|
||||
the_tree_native::signed_add(~int64_t(row[n.capacity]),1));
|
||||
dynamic=the_tree_native::signed_add(int64_t(row[n.dynamic]),
|
||||
signed_product(excess>0?excess:0,context.congestion));
|
||||
}
|
||||
const int64_t cost=the_tree_native::signed_add(int64_t(base),dynamic);
|
||||
Array pair;pair.push_back(neighbor);pair.push_back(cost);result.push_back(pair);
|
||||
// Same short circuit and first invalid prefix as the original method.
|
||||
if (cost<=0 || !indices.has(neighbor)) break;
|
||||
}
|
||||
shared[current]=result;rows=result;return true;
|
||||
}
|
||||
public:
|
||||
Dictionary guest_tree_edge_rows(Object *path,const Vector3i ¤t,const Array &layers,const Array &access) {
|
||||
const WalkEdgeNames names;WalkEdgeContext context;Array rows;
|
||||
Dictionary result;const StringName supported{"supported",false}, row_key{"rows",false};
|
||||
result[supported]=false;
|
||||
if (!walk_edge_context(names,path,layers,access,context) ||
|
||||
!builtin_walk_edge_rows(names,context,current,rows)) return result;
|
||||
result[supported]=true;result[row_key]=rows;return result;
|
||||
}
|
||||
private:
|
||||
Dictionary search_impl(const Array &cells, const Dictionary &indices, I start_index,
|
||||
const Dictionary &goals, const Callable &edge_provider, bool use_edge_cache) {
|
||||
Dictionary output;
|
||||
output[StringName("status")] = String("unusable");
|
||||
if (start_index < 0 || start_index >= cells.size() || !edge_provider.is_valid()) return output;
|
||||
try {
|
||||
std::vector<I> goal_indices;
|
||||
const Array goal_keys = goals.keys();
|
||||
for (I j = 0; j < goal_keys.size(); ++j)
|
||||
goal_indices.push_back(static_cast<I>(indices.get(goal_keys[j], I(-1))));
|
||||
Array arguments;
|
||||
arguments.resize(1);
|
||||
const WalkEdgeNames walk_names;WalkEdgeContext walk_context;
|
||||
if (edge_provider.get_method()==walk_names.provider && edge_provider.get_argument_count()==1 &&
|
||||
edge_provider.get_bound_arguments_count()==2 && edge_provider.get_unbound_arguments_count()==0) {
|
||||
const Array bound=edge_provider.get_bound_arguments();
|
||||
if (bound.size()==2 && bound[0].get_type()==Variant::ARRAY && bound[1].get_type()==Variant::ARRAY)
|
||||
walk_edge_context(walk_names,edge_provider.get_object(),bound[0],bound[1],walk_context);
|
||||
}
|
||||
// Call the original bound Godot edge method only when the search expands a node.
|
||||
// Preserve its lazy cache population, filter ordering and first invalid row.
|
||||
auto original_edges = [&](I current) {
|
||||
arguments[0] = cells[current];
|
||||
Array fused;bool fused_supported=false;
|
||||
if (cells[current].get_type()==Variant::VECTOR3I) {
|
||||
const Vector3i cell=cells[current];
|
||||
fused_supported=builtin_walk_edge_rows(walk_names,walk_context,cell,fused);
|
||||
}
|
||||
const Variant value = fused_supported ? Variant(fused) : edge_provider.callv(arguments);
|
||||
if (value.get_type() != Variant::ARRAY) return std::vector<Edge>{{-1, 0}};
|
||||
const Array rows = value;
|
||||
std::vector<Edge> result;
|
||||
result.reserve(rows.size());
|
||||
for (I j = 0; j < rows.size(); ++j) {
|
||||
if (rows[j].get_type() != Variant::ARRAY) { result.push_back({-1, 0}); break; }
|
||||
const Array row = rows[j];
|
||||
if (row.size() != 2 || row[1].get_type() != Variant::INT) {
|
||||
result.push_back({-1, 0}); break;
|
||||
}
|
||||
const I cost = static_cast<I>(row[1]);
|
||||
// Nonpositive cost is rejected before requesting the neighbor index.
|
||||
if (cost <= 0) { result.push_back({-1, cost}); break; }
|
||||
const I neighbor = static_cast<I>(indices.get(row[0], I(-1)));
|
||||
result.push_back({neighbor, cost});
|
||||
if (neighbor < 0) break;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
auto edges = [&](I current) {
|
||||
if (!use_edge_cache) return original_edges(current);
|
||||
const auto found = cached_edges.find(current);
|
||||
if (found != cached_edges.end()) return found->second;
|
||||
// Populate only when the old algorithm expands this same node.
|
||||
// Retain original provider validation and first-invalid prefix.
|
||||
auto converted = original_edges(current);
|
||||
cached_edges.emplace(current, converted);
|
||||
return converted;
|
||||
};
|
||||
const auto result = the_tree_native::search(cells.size(), start_index, goal_indices, edges);
|
||||
output[StringName("status")] = String(result.status.c_str());
|
||||
if (result.status != "usable") return output;
|
||||
PackedInt64Array costs, parents;
|
||||
if (costs.resize(result.costs.size()) != OK || parents.resize(result.parents.size()) != OK) {
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
for (I j = 0; j < cells.size(); ++j) { costs.set(j, result.costs[j]); parents.set(j, result.parents[j]); }
|
||||
TypedArray<int64_t> discovered, parent_discovered;
|
||||
for (I j : result.discovered) discovered.push_back(j);
|
||||
for (I j : result.parent_discovered) parent_discovered.push_back(j);
|
||||
Dictionary settled;
|
||||
for (I j : result.settled) settled[cells[j]] = true;
|
||||
output[StringName("cost_values")] = costs;
|
||||
output[StringName("parent_indices")] = parents;
|
||||
output[StringName("discovered")] = discovered;
|
||||
output[StringName("parent_discovered")] = parent_discovered;
|
||||
output[StringName("settled")] = settled;
|
||||
return output;
|
||||
} catch (const std::exception &) {
|
||||
output = Dictionary();
|
||||
output[StringName("status")] = String("unusable");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void initialize_tree_numeric_core(ModuleInitializationLevel level) {
|
||||
if (level == MODULE_INITIALIZATION_LEVEL_SCENE) GDREGISTER_CLASS(TheTreeNumericCore);
|
||||
}
|
||||
void uninitialize_tree_numeric_core(ModuleInitializationLevel) {}
|
||||
|
||||
extern "C" {
|
||||
GDExtensionBool GDE_EXPORT tree_numeric_core_init(GDExtensionInterfaceGetProcAddress get_proc,
|
||||
GDExtensionClassLibraryPtr library, GDExtensionInitialization *initialization) {
|
||||
GDExtensionBinding::InitObject init(get_proc, library, initialization);
|
||||
init.register_initializer(initialize_tree_numeric_core);
|
||||
init.register_terminator(uninitialize_tree_numeric_core);
|
||||
init.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
return init.init();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user