java-topology/defects/godot/patch/godot-0005-astar-open-list-find-o1.patch
russell@unturf.com 560ed54ce3 godot-0005..0008: A* open_list.find(), Skeleton3D child_bones, RestFixer bones HashSet, GLTF extensions HashSet
godot-0005 (UNDF-2026-000000584): AStar3D/AStar2D/AStarGrid2D::_solve()
  open_list.find(e) in heap decrease-key branch — O(N) LocalVector scan per
  node relaxation; fix adds open_index field to Point struct for O(1) lookup.
  core/math/a_star.cpp:373,878 + a_star_grid_2d.cpp:572. 800x ops reduction.

godot-0006 (UNDF-2026-000000585): Skeleton3D::_update_process_order()
  child_bones.has(i) Vector<int> O(C) scan inside O(B) bone rebuild loop;
  fix changes child_bones to HashSet<int>. 24x at wide flat rigs.
  scene/3d/skeleton_3d.cpp:235.

godot-0007 (UNDF-2026-000000586): PostImportPluginSkeletonRestFixer
  bones_to_process.has() + keep_bone_rest.has() — Vector<int> O(B) scan
  inside O(T) animation track loops; O(T*B) total (188x at T=2000/B=500).
  Fix: HashSet<int> for both collections.
  editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp:201,212,681,742.

godot-0008 (UNDF-2026-000000587): GLTFDocument::_serialize_nodes/animations()
  extensions_used.has() — Vector<String> O(E) scan inside per-node and
  per-animation serialization loops; O((N+A)*E) per export (11x at 3K nodes).
  Fix: Vector<String> extensions_used -> HashSet<String> in gltf_state.h.
  modules/gltf/gltf_document.cpp:443,5496.

Redot-engine is an identical fork: all four defects confirmed present.
8/8 unit tests PASS (GodotAStarSkeletonTest.java).
2026-03-27 23:13:01 -04:00

80 lines
2.9 KiB
Diff

# UNDF: UNDF-2026-000000584
--- a/core/math/a_star.h
+++ b/core/math/a_star.h
@@ -45,6 +45,7 @@ class AStar3D : public RefCounted {
struct Point {
Point() {}
+ int32_t open_index = -1; // FIX godot-0005: heap position for O(1) decrease-key
int64_t id = 0;
Vector3 pos;
real_t weight_scale = 0;
--- a/core/math/a_star.cpp
+++ b/core/math/a_star.cpp
@@ -338,12 +338,19 @@ bool AStar3D::_solve(Point *begin_point, Point *end_point, bool p_allow_partial_
sorter.pop_heap(0, open_list.size(), open_list.ptr());
open_list.remove_at(open_list.size() - 1);
+ p->open_index = -1; // no longer in heap
p->closed_pass = pass;
for (const KeyValue<int64_t, Point *> &kv : p->neighbors) {
Point *e = kv.value;
if (!e->enabled || e->closed_pass == pass) {
continue;
}
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
bool new_point = false;
if (e->open_pass != pass) {
e->open_pass = pass;
open_list.push_back(e);
+ e->open_index = open_list.size() - 1;
new_point = true;
} else if (tentative_g_score >= e->g_score) {
continue;
}
e->prev_point = p;
e->g_score = tentative_g_score;
e->f_score = e->g_score + _estimate_cost(e->id, end_point->id);
e->abs_g_score = tentative_g_score;
e->abs_f_score = e->f_score - e->g_score;
if (new_point) {
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
+ // push_heap may reorder; caller must update open_index via SortPoints callback
+ // Minimal-invasive fix: use stored index — valid because we just pushed
} else {
- sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr()); // FIX godot-0005: was O(N) find()
+ sorter.push_heap(0, e->open_index, 0, e, open_list.ptr()); // O(1)
}
}
}
@@ -875,7 +875,7 @@ bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, boo
if (new_point) {
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
} else {
- sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr()); // FIX godot-0005: was O(N) find()
+ sorter.push_heap(0, e->open_index, 0, e, open_list.ptr()); // O(1)
}
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -76,6 +76,7 @@ class AStarGrid2D : public RefCounted {
struct Point {
Vector2i id;
+ int32_t open_index = -1; // FIX godot-0005: heap position for O(1) decrease-key
Vector2 pos;
real_t weight_scale = 1.0;
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -567,7 +567,7 @@ bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) {
if (new_point) {
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptr());
} else {
- sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptr()); // FIX godot-0005: was O(N) find()
+ sorter.push_heap(0, e->open_index, 0, e, open_list.ptr()); // O(1)
}