OverviewFilesCommitsIssuesDocsReleasesPeers

dc-terrain commits / 0eb2676

dc: the scalar path is a map-load cost, not a per-frame one -- measured
hyper-games #64 NEXT 3: measure collision query volume in-game, then decide the scalar path with a number instead of a guess. THERE WAS NO COUNTER, so the question could not be answered. Two entry points cover all of it -- sample_density (map scripts: spawn placement, _surface_y, _lift_out_of_terrain, the fall-respawn) and _eval_with_edits (get_distance, get_surface_normal, get_capsule_push, the SDF collision family). Both now count, DCTerrain.get_scalar_query_counts() is bound, and DC_SCALAR_PROFILE=1 prints the totals once a second. Env-gated rather than a property so measuring costs no game-script change and a normal run gets no extra line. MEASURED, on the real maps, through scripts/dev-run.sh --no-daemon: map_dc_erosion 100 s sample_density 524 by t=3.5s, then FLAT eval_with_edits 0 collision_mode 0 (TRIMESH) map_dc_diffusion 70 s sample_density 240 by t=4.7s, 1050 by t=9.2s, then FLAT for 60 s eval_with_edits 0 collision_mode 0 (TRIMESH) THE DECISION. The scalar path costs 524 and 1050 calls PER MAP LOAD and exactly ZERO per frame. The SDF collision family is never called at all: collision_mode_ defaults to COLLISION_TRIMESH and no map sets it, so collision comes from the meshed trimesh and DCCollisionSDF is dead at runtime. Optimising the scalar path -- giving it evaluate_batch, a cache, a cheaper approximation -- buys nothing measurable. It is a correctness surface, not a performance one, which is the opposite of what "collision query volume" suggests and is exactly why #64 asked for the number. ⚠ WHAT THIS DOES NOT SAY. Both runs sit at the spawn: nobody moved, nothing was shot, no terrain was edited. A run with a player walking, or with edits, could put traffic through _eval_with_edits that these do not -- and if a map ever sets collision_mode to SDF, every frame of character movement lands on this path and the answer inverts. The counter is committed so that run is a command, not another project. The same log line also reports gpu_density=100% and cpu_density_fallback=0 on both maps, so the CPU grid fill wired up in the commit two before this one is not on the hot path either while the GPU is healthy. It is the fallback, and the fallback is what runs when the GPU declines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0eb267617c9acf7b7d70e601542618f3571ba252
by plankatron <fleeky@gmail.com> · 2026-09-06T15:04:18.000Z

Changed files (2) +50 -0

src/dc_terrain.cpp +34 -0
……@@ -519,7 +519,37 @@ void DCTerrain::_ready() {
519519 // here — _ready() only fires once but ENTER_TREE handles lifecycle.
520520}
521521
522Dictionary DCTerrain::get_scalar_query_counts() const {
523 Dictionary d;
524 d["sample_density"] = (int64_t)n_sample_density_.load(std::memory_order_relaxed);
525 d["eval_with_edits"] = (int64_t)n_eval_with_edits_.load(std::memory_order_relaxed);
526 d["collision_mode"] = (int)collision_mode_;
527 return d;
528}
529
530void DCTerrain::reset_scalar_query_counts() {
531 n_sample_density_.store(0, std::memory_order_relaxed);
532 n_eval_with_edits_.store(0, std::memory_order_relaxed);
533}
534
522535void DCTerrain::_process(double delta) {
536 // DC_SCALAR_PROFILE=1 prints the scalar query rate once a second. Off by
537 // default and env-gated rather than a property, so measuring costs no game
538 // script change and normal runs get no extra log line. #64 NEXT 3.
539 if (scalar_profile_accum_ == 0.0) {
540 scalar_profile_on_ = OS::get_singleton()->get_environment("DC_SCALAR_PROFILE") == "1";
541 }
542 if (scalar_profile_on_) {
543 scalar_profile_accum_ += delta;
544 if (scalar_profile_accum_ >= 1.0) {
545 UtilityFunctions::print(
546 "DC_SCALAR_PROFILE ", (double)scalar_profile_accum_, "s: sample_density=",
547 (int64_t)n_sample_density_.load(std::memory_order_relaxed),
548 " eval_with_edits=", (int64_t)n_eval_with_edits_.load(std::memory_order_relaxed),
549 " collision_mode=", (int)collision_mode_);
550 scalar_profile_accum_ = 0.0001;
551 }
552 }
523553 if (!is_inside_tree()) return;
524554
525555 frame_counter_++;
……@@ -3162,6 +3192,7 @@ PackedFloat32Array DCTerrain::debug_gpu_density(const Vector3 &origin, float vox
31623192}
31633193
31643194float DCTerrain::sample_density(const Vector3 &position) const {
3195 n_sample_density_.fetch_add(1, std::memory_order_relaxed);
31653196 float d = generator_ptr_->evaluate(position.x, position.y, position.z, 0.0f);
31663197 if (config_.edits && !config_.edits->empty()) {
31673198 d = DCEditList::apply(*config_.edits, position.x, position.y, position.z, d);
……@@ -3176,6 +3207,7 @@ float DCTerrain::sample_density(const Vector3 &position) const {
31763207// Single-point queries: O(all_edits) is fine (one call, not 32K).
31773208// ---------------------------------------------------------------------------
31783209float DCTerrain::_eval_with_edits(float x, float y, float z) const {
3210 n_eval_with_edits_.fetch_add(1, std::memory_order_relaxed);
31793211 float d = generator_ptr_->evaluate(x, y, z, 0.0f);
31803212 if (config_.edits && !config_.edits->empty()) {
31813213 d = DCEditList::apply(*config_.edits, x, y, z, d);
……@@ -4377,6 +4409,8 @@ void DCTerrain::_bind_methods() {
43774409 // there is no way for a test, or a map script, to ask whether the terrain
43784410 // on screen is the one the assigned generator describes.
43794411 ClassDB::bind_method(D_METHOD("is_using_builtin_generator"), &DCTerrain::is_using_builtin_generator);
4412 ClassDB::bind_method(D_METHOD("get_scalar_query_counts"), &DCTerrain::get_scalar_query_counts);
4413 ClassDB::bind_method(D_METHOD("reset_scalar_query_counts"), &DCTerrain::reset_scalar_query_counts);
43804414 ClassDB::bind_method(D_METHOD("sample_density", "position"), &DCTerrain::sample_density);
43814415 ClassDB::bind_method(D_METHOD("debug_gpu_density", "origin", "voxel_size", "cells"), &DCTerrain::debug_gpu_density);
43824416 ClassDB::bind_method(D_METHOD("debug_collision_faces", "center", "radius"), &DCTerrain::debug_collision_faces);
src/dc_terrain.h +16 -0
……@@ -181,6 +181,16 @@ public:
181181
182182 // -- Density sampling (for spawn placement, etc.) --
183183 bool is_using_builtin_generator() const { return using_builtin_generator_; }
184
185 // SCALAR QUERY VOLUME. hyper-games #64 NEXT 3 asks for a number instead of
186 // a guess about the scalar path, and there was no counter to read.
187 //
188 // Two entry points cover all of it: sample_density (map scripts -- spawn
189 // placement, _lift_out_of_terrain, the fall-respawn) and _eval_with_edits
190 // (get_distance, get_surface_normal, get_capsule_push -- the SDF collision
191 // family, which only runs when collision_mode is SDF).
192 Dictionary get_scalar_query_counts() const;
193 void reset_scalar_query_counts();
184194 float sample_density(const Vector3 &position) const;
185195
186196 // THE GPU ORACLE. Runs the real density pipeline -- the same shader, the
……@@ -383,6 +393,12 @@ private:
383393 // Warn-once latch for the built-in-generator substitution below.
384394 bool warned_generator_fallback_ = false;
385395 bool using_builtin_generator_ = true;
396 // mutable + atomic: the query entry points are const and DCCollisionSDF is
397 // reachable from more than one thread.
398 mutable std::atomic<uint64_t> n_sample_density_{0};
399 mutable std::atomic<uint64_t> n_eval_with_edits_{0};
400 double scalar_profile_accum_ = 0.0;
401 bool scalar_profile_on_ = false;
386402 void _fallback_to_builtin(const char *where);
387403 Ref<DCTerrainGenerator> generator_resource_; // user-assigned Resource (inspector/script)
388404