Skip to content

WorldScape3D - #1329

Open
OldDev78 wants to merge 36 commits into
Redot-Engine:masterfrom
OldDev78:worldscape3d
Open

WorldScape3D#1329
OldDev78 wants to merge 36 commits into
Redot-Engine:masterfrom
OldDev78:worldscape3d

Conversation

@OldDev78

@OldDev78 OldDev78 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Ported Terrain3D integration from ReX.
Now renamed as WorldScape3D.

Summary by CodeRabbit

  • New Features
    • Added WorldScape 3D terrain support with regions, maps, materials, collision, navigation, mesh generation, LODs, and instancing.
    • Added editor tools for sculpting, painting, gradients, vegetation placement, region management, undo/redo, and terrain visualization.
    • Added texture and mesh asset management with thumbnails, LOD settings, and drag-and-drop workflows.
    • Added texture channel packing, image import/export, baking, navigation setup, and built-in terrain brushes.
  • Documentation
    • Added comprehensive reference documentation for WorldScape 3D classes, resources, properties, methods, signals, and constants.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds the WorldScape 3D module with terrain data, rendering, collision, instancing, asset management, editor tools, build integration, embedded brush resources, and public API documentation.

Changes

WorldScape 3D module

Layer / File(s) Summary
Module foundation and runtime contracts
core/templates/cowdata.h, modules/worldscape_3d/SCsub, modules/worldscape_3d/config.py, modules/worldscape_3d/constants.h, modules/worldscape_3d/register_types.*, modules/worldscape_3d/worldscape_3d.*, modules/worldscape_3d/generated_texture.*, modules/worldscape_3d/logger.h, modules/worldscape_3d/target_node_3d.h
Adds build configuration, shared constants, logging, module registration, generated textures, target tracking, and the WorldScape3D node API.
Terrain data and rendering runtime
modules/worldscape_3d/worldscape_3d_data.*, worldscape_3d_region.*, worldscape_3d_assets.*, worldscape_3d_material.*, worldscape_3d_collision.*, worldscape_3d_instancer.*, worldscape_3d_mesher.*, worldscape_3d_util.*
Adds terrain regions, map persistence and editing, texture and mesh assets, shader materials, collision generation, foliage instances, clipmap meshing, and image utilities.
Editor terrain operations
modules/worldscape_3d/editor/worldscape_3d_editor.*, modules/worldscape_3d/editor/gizmos.*
Adds terrain editing, painting, undo and redo, viewport input, plugin lifecycle handling, navigation and instancing tools, and region gizmos.
Editor UI and workflows
modules/worldscape_3d/editor/ui/*, modules/worldscape_3d/editor/menu/*
Adds tool settings, toolbar controls, gradient operations, asset management, baking, navigation setup, channel packing, directory setup, and embedded brush resources.
Public API documentation
modules/worldscape_3d/doc_classes/*, doc/classes/WorldScape3DAssetResource.xml
Documents the WorldScape 3D node, resources, terrain data, editor APIs, asset types, collision modes, materials, regions, and instancing APIs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🔵 Low · up to a3e1e

The null-editor handling can leave stale terrain gizmo geometry visible after the editor is gone. This is a bounded correctness issue that should be fixed or explicitly accepted before merging.

Sequence Diagram(s)

sequenceDiagram
  participant Editor as WorldScape3DEditor
  participant UI as WorldScape3DUI
  participant Terrain as WorldScape3D
  participant Data as WorldScape3DData
  participant Renderer as WorldScape3DMesher

  Editor->>UI: Select tool and provide brush data
  UI->>Editor: Forward operation and viewport input
  Editor->>Data: Edit region maps
  Data->>Terrain: Emit map and edited-area changes
  Terrain->>Renderer: Update terrain mesh and rendering state
  Renderer-->>Editor: Refresh visible terrain
Loading

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly identifies the main feature added by the pull request: the WorldScape3D module and integration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@OldDev78
OldDev78 marked this pull request as ready for review August 12, 2026 00:47
@OldDev78
OldDev78 requested review from a team August 12, 2026 00:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟡 Minor comments (42)
modules/worldscape_3d/doc_classes/WorldScape3DEditor.xml-118-119 (1)

118-119: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a description for the SCULPT constant.

All other Tool constants have descriptions. SCULPT has an empty body. SCULPT (1) and HEIGHT (2) are separate values, so readers cannot tell them apart.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DEditor.xml` around lines 118 -
119, Add a descriptive documentation body for the SCULPT constant in the Tool
enum, distinguishing it from HEIGHT and explaining its sculpting purpose.
Preserve the existing constant name, value, and surrounding documentation
structure.
modules/worldscape_3d/doc_classes/WorldScape3DMaterial.xml-60-63 (1)

60-63: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete two member descriptions and fix one grammar error.

auto_shader_enabled and dual_scaling_enabled have empty descriptions. Line 149 contains "using a the nearest pixel only".

✏️ Proposed fix for the grammar error
-			Textures are filtered using a the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale.
+			Textures are filtered using the nearest pixel only. It is faster than LINEAR, but the texture will look pixelated. Use this for a low-poly look, with a very low uv_scale.

Also applies to: 148-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DMaterial.xml` around lines 60 -
63, Complete the XML descriptions for the auto_shader_enabled and
dual_scaling_enabled members, describing each setting’s behavior consistently
with the surrounding WorldScape3DMaterial documentation. In the affected
description around the nearest-pixel explanation, remove the extra article so
the wording uses “using the nearest pixel only.”
modules/worldscape_3d/doc_classes/WorldScape3DData.xml-82-87 (1)

82-87: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

External documentation URLs were carried over from the upstream project. The shared root cause is that the rename to WorldScape3D did not update the external link hosts and targets. The links now point to Terrain3D and Godot documentation, and one uses a path form that differs from the other links in this PR.

  • modules/worldscape_3d/doc_classes/WorldScape3DData.xml#L82-L87: remove or replace the terrain3d.readthedocs.io tips link. The same link also appears on lines 139 and 177.
  • modules/worldscape_3d/doc_classes/WorldScape3DRegion.xml#L111-L115: change the docs.godotengine.org host to docs.redotengine.org and remove or replace the terrain3d.readthedocs.io control map link.
  • modules/worldscape_3d/doc_classes/WorldScape3DInstancer.xml#L18-L18: add the .html suffix to the class_multimesh link so it matches the other class links.
  • modules/worldscape_3d/doc_classes/WorldScape3DMeshAsset.xml#L9-L9: add the .html suffix to the class_multimesh link and point the MultiMeshInstance3D label at class_multimeshinstance3d.html.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DData.xml` around lines 82 - 87,
Update the documentation links in WorldScape3DData.xml lines 82-87, 139, and 177
by removing or replacing the terrain3d.readthedocs.io tips links; in
WorldScape3DRegion.xml lines 111-115, change docs.godotengine.org to
docs.redotengine.org and remove or replace the terrain3d.readthedocs.io
control-map link; in WorldScape3DInstancer.xml line 18, add .html to the
class_multimesh link; and in WorldScape3DMeshAsset.xml line 9, add .html to
class_multimesh and point MultiMeshInstance3D to class_multimeshinstance3d.html.
modules/worldscape_3d/doc_classes/WorldScape3DInstancer.xml-14-14 (1)

14-14: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix two wording errors.

Line 14 contains "this is can be used procedurally". Lines 74 describe append_region as acting "within a region location", but the parameter is a WorldScape3DRegion object.

✏️ Proposed fix
-		- [method remove_instances] - Like add_instances, this is can be used procedurally but is designed for hand editing.
+		- [method remove_instances] - Like add_instances, this can be used procedurally but is designed for hand editing.
-				Appends new transforms to the existing data within a region location. The mesh_id should already be setup as a [WorldScape3DMeshAsset] in the asset dock.
+				Appends new transforms to the existing data within the specified region. The mesh_id should already be setup as a [WorldScape3DMeshAsset] in the asset dock.

Also applies to: 66-77

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DInstancer.xml` at line 14,
Correct the wording in the WorldScape3DInstancer documentation: revise the
remove_instances description to replace “this is can be used” with grammatical
text, and update the append_region description to refer to operating on a
WorldScape3DRegion object rather than “within a region location.”
modules/worldscape_3d/doc_classes/WorldScape3D.xml-44-44 (1)

44-44: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct two text errors in get_intersection.

Line 44 contains "This operation is does not use physics". Line 64 calls 3.402823466e+38 the "maximum double float value". That value is the maximum single-precision float.

✏️ Proposed fix
-				Casts a ray from [code skip-lint]src_pos[/code] pointing towards [code skip-lint]direction[/code], attempting to intersect the terrain. This operation is does not use physics, so enabling collision is unnecessary.
+				Casts a ray from [code skip-lint]src_pos[/code] pointing towards [code skip-lint]direction[/code], attempting to intersect the terrain. This operation does not use physics, so enabling collision is unnecessary.
-				- If there is no intersection, eg. the ray points towards the sky, it returns the maximum double float value [code skip-lint]Vector3(3.402823466e+38F,...)[/code]. You can check this case with this code: [code skip-lint]if point.z > 3.4e38:[/code]
+				- If there is no intersection, eg. the ray points towards the sky, it returns the maximum single-precision float value [code skip-lint]Vector3(3.402823466e+38F,...)[/code]. You can check this case with this code: [code skip-lint]if point.z > 3.4e38:[/code]

Also applies to: 64-64

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3D.xml` at line 44, Correct the
documentation text for get_intersection: change “is does not use physics” to
grammatically correct wording, and update the description of 3.402823466e+38 to
identify it as the maximum single-precision float rather than the maximum
double-precision value.
modules/worldscape_3d/doc_classes/WorldScape3DRegion.xml-154-156 (1)

154-156: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the grammar in vertex_spacing.

The text contains "This value is manage by the instancer".

✏️ Proposed fix
-			Stored instancer transforms are laterally scaled by this value. This value is manage by the instancer on loading or when [member WorldScape3D.vertex_spacing] is set, and shouldn't be manually adjusted.
+			Stored instancer transforms are laterally scaled by this value. The instancer manages this value on loading or when [member WorldScape3D.vertex_spacing] is set. Do not adjust it manually.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DRegion.xml` around lines 154 -
156, Update the documentation text for the vertex_spacing member to correct the
grammar in “This value is manage by the instancer,” while preserving the
existing meaning and surrounding guidance.
modules/worldscape_3d/doc_classes/WorldScape3D.xml-150-150 (1)

150-150: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stray backtick.

The line ends with `[method get_intersection].`` The backtick renders in the class reference.

✏️ Proposed fix
-			See [method get_intersection].`
+			See [method get_intersection].
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3D.xml` at line 150, Remove the
trailing backtick from the get_intersection method reference in the WorldScape3D
documentation, leaving the sentence ending with a period so the backtick is not
rendered.
modules/worldscape_3d/doc_classes/WorldScape3DData.xml-421-431 (1)

421-431: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use the declared parameter name in the save_region description.

The parameter is save_16_bit. The description lists it as 16_bit.

✏️ Proposed fix
-				- 16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation.
+				- save_16_bit - converts the edited 32-bit heightmap to 16-bit. This is a lossy operation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DData.xml` around lines 421 -
431, Update the save_region method description to refer to the declared
save_16_bit parameter name instead of 16_bit, while preserving the existing
explanation of the lossy 32-bit-to-16-bit conversion.
modules/worldscape_3d/doc_classes/WorldScape3DInstancer.xml-23-30 (1)

23-30: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the stale tool_settings.gd reference.

This module implements the tool settings bar in C++ (modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp). The description points readers to a GDScript file from the original GDExtension addon.

✏️ Proposed fix
-				Used during terrain editing to place instances, given many brush parameters. In addition to the brush position, it also uses the following parameters: asset_id, size, strength, fixed_scale, random_scale, fixed_spin, random_spin, fixed_tilt, random_tilt, align_to_normal, height_offset, random_height, vertex_color, random_hue, random_darken. All of these settings are set in the editor through tool_settings.gd.
+				Used during terrain editing to place instances, given many brush parameters. In addition to the brush position, it also uses the following parameters: asset_id, size, strength, fixed_scale, random_scale, fixed_spin, random_spin, fixed_tilt, random_tilt, align_to_normal, height_offset, random_height, vertex_color, random_hue, random_darken. All of these settings are set in the editor tool settings bar.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DInstancer.xml` around lines 23
- 30, Remove the stale “tool_settings.gd” reference from the add_instances
method description in WorldScape3DInstancer documentation, while preserving the
list of brush parameters and the existing description of terrain editing
behavior.
modules/worldscape_3d/doc_classes/WorldScape3DMeshAsset.xml-63-63 (1)

63-63: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the issue tracker reference.

The URL points to github.com/godotengine/godot/issues/102799. The link text calls it "Redot issue #102799". Use a label that matches the host, or link the corresponding Redot issue.

✏️ Proposed fix
-			Currently broken and hidden in the inspector until [url=https://github.com/godotengine/godot/issues/102799]Redot issue `#102799`[/url] is fixed.
+			Currently broken and hidden in the inspector until [url=https://github.com/godotengine/godot/issues/102799]Godot issue `#102799`[/url] is fixed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DMeshAsset.xml` at line 63,
Correct the issue reference in the documentation text around the
WorldScape3DMeshAsset inspector note: either relabel it as a Godot issue to
match the existing godotengine URL or update the URL to the corresponding Redot
issue while preserving the note’s meaning.
modules/worldscape_3d/doc_classes/WorldScape3DData.xml-296-303 (1)

296-303: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the failure return value for get_roughness.

The method returns float. The description states it returns Color(NAN, NAN, NAN, NAN).

✏️ Proposed fix
-				Returns [code skip-lint]Color(NAN, NAN, NAN, NAN)[/code] if the position is outside of defined regions.
+				Returns [code skip-lint]NAN[/code] if the position is outside of defined regions.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DData.xml` around lines 296 -
303, Update the get_roughness documentation to describe a float failure value
rather than Color(NAN, NAN, NAN, NAN), using the appropriate NaN representation
for float when the position is outside defined regions.
modules/worldscape_3d/doc_classes/WorldScape3DData.xml-85-85 (1)

85-85: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the broken Tips links

The tips.html#using-the-generated-height-map-in-other-shaders URL returns 404 in all three WorldScape3DData.xml descriptions at lines 85, 139, and 177. Replace it with a valid documentation URL or remove the link. The controlmap_format.html link in WorldScape3DRegion.xml resolves successfully.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/doc_classes/WorldScape3DData.xml` at line 85, Update
the three WorldScape3DData.xml descriptions referencing
“using-the-generated-height-map-in-other-shaders” to use a valid documentation
URL, or remove those Tips links while preserving the surrounding API
descriptions.
doc/classes/WorldScape3DAssetResource.xml-1-10 (1)

1-10: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move WorldScape3DAssetResource.xml to the module documentation directory.

Add WorldScape3DAssetResource to get_doc_classes(), move the file to modules/worldscape_3d/doc_classes/, and change the schema path to ../../../doc/class.xsd.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@doc/classes/WorldScape3DAssetResource.xml` around lines 1 - 10, Move the
WorldScape3DAssetResource documentation file into the module’s doc_classes
directory, update its xsi:noNamespaceSchemaLocation to ../../../doc/class.xsd,
and add WorldScape3DAssetResource to the module’s get_doc_classes() list.
modules/worldscape_3d/worldscape_3d.cpp-1010-1014 (1)

1010-1014: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use get_physics_process_delta_time() in the physics notification.

This case handles NOTIFICATION_PHYSICS_PROCESS but reads get_process_delta_time(), which returns the idle frame delta. The two values differ when the physics tick rate does not match the frame rate.

__physics_process does not currently use p_delta, so behavior does not change today. The value becomes wrong as soon as the parameter is used.

🐛 Proposed fix
-			__physics_process(get_process_delta_time());
+			__physics_process(get_physics_process_delta_time());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d.cpp` around lines 1010 - 1014, Update the
NOTIFICATION_PHYSICS_PROCESS branch to pass get_physics_process_delta_time() to
__physics_process instead of get_process_delta_time(), preserving the existing
physics notification flow.
modules/worldscape_3d/worldscape_3d_assets.cpp-266-269 (1)

266-269: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Report normal_mipmaps in the normal-map mismatch message.

The message on Line 268 compares against albedo_mipmaps. The surrounding block validates the normal texture and compares mipmaps against normal_mipmaps on Line 266. The printed reference value is therefore wrong and misleads the user during texture preparation.

🐛 Proposed fix
-					LOG(ERROR, "Texture ID ", i, " normal mipmap setting (", mipmaps, ") doesn't match first texture (", albedo_mipmaps, "). They must be identical. Read Texture Prep in docs.");
+					LOG(ERROR, "Texture ID ", i, " normal mipmap setting (", mipmaps, ") doesn't match first texture (", normal_mipmaps, "). They must be identical. Read Texture Prep in docs.");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_assets.cpp` around lines 266 - 269,
Update the mismatch log in the normal-map validation block to report
normal_mipmaps as the reference value instead of albedo_mipmaps, while
preserving the existing comparison and warning behavior.
modules/worldscape_3d/worldscape_3d_assets.cpp-571-571 (1)

571-571: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp each component of p_size separately.

CLAMP expands to comparisons with operator< and operator>. For Vector2i, those operators compare lexicographically, not per component. A value such as Vector2i(5000, 1) compares greater than Vector2i(4096, 4096) and is replaced entirely by Vector2i(4096, 4096). The y component changes from 1 to 4096.

🐛 Proposed fix
-	Vector2i size = CLAMP(p_size, Vector2i(1, 1), Vector2i(4096, 4096));
+	Vector2i size = Vector2i(CLAMP(p_size.x, 1, 4096), CLAMP(p_size.y, 1, 4096));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_assets.cpp` at line 571, Update the size
calculation to clamp the x and y components of p_size independently to the [1,
4096] range, rather than applying CLAMP to the Vector2i as a whole; preserve
valid component values without allowing either dimension outside the bounds.
modules/worldscape_3d/worldscape_3d_util.cpp-63-68 (1)

63-68: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the cast result before dereferencing obj.

Line 64 casts a Variant to Object *. Line 65 dereferences the result. A Variant with type OBJECT can hold a null pointer or refer to an object that was already freed. The dereference then crashes.

Line 65 runs in all build configurations. Only the LOG call on Line 66 is removed in release builds, so the guard cannot be omitted.

The same pattern appears in print_dict at Lines 92-93.

🛡️ Proposed fix
 			case Variant::OBJECT: {
 				Object *obj = cast_to<Object>(var);
-				String str = "Object#" + String::num_uint64(obj->get_instance_id()) + ", " + ptr_to_str(obj);
+				String str = obj ? "Object#" + String::num_uint64(obj->get_instance_id()) + ", " + ptr_to_str(obj) : "<null>";
 				LOG(p_level, i, ": ", str);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_util.cpp` around lines 63 - 68, Guard the
Object* result in the Variant::OBJECT branch before calling get_instance_id() or
ptr_to_str(), handling null or invalid references without dereferencing them.
Apply the same protection to the corresponding object handling in print_dict,
and keep the guard active in all build configurations.
modules/worldscape_3d/config.py-13-25 (1)

13-25: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add WorldScape3DAssetResource to the module documentation map.

register_types.cpp registers this class, but get_doc_classes() does not map it to the module path. DocTools::save_classes therefore uses the default doc/classes path. Move its XML to modules/worldscape_3d/doc_classes/ and add the class name to get_doc_classes().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/config.py` around lines 13 - 25, Update
get_doc_classes() to include WorldScape3DAssetResource, matching the class
registered in register_types.cpp. Move its XML documentation from the default
doc/classes location into modules/worldscape_3d/doc_classes/ so
DocTools::save_classes maps it to the module documentation path.
modules/worldscape_3d/worldscape_3d_util.h-98-123 (1)

98-123: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make is_power_of_2 constexpr. The math types support the conversion, bilerp, and aabb2rect functions. However, is_valid_region_size cannot be evaluated at compile time because is_power_of_2 is not constexpr.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_util.h` around lines 98 - 123, Make the
existing is_power_of_2 function constexpr so is_valid_region_size can be
evaluated at compile time, while preserving its current behavior and interface.
modules/worldscape_3d/worldscape_3d_mesh_asset.cpp-386-390 (1)

386-390: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The log prints the wrong member.

Line 389 reports the generated size but prints _generated_faces. Print _generated_size.

-		LOG(INFO, "Setting generated size: ", _generated_faces);
+		LOG(INFO, "Setting generated size: ", _generated_size);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_mesh_asset.cpp` around lines 386 - 390,
Update the logging statement in WorldScape3DMeshAsset::set_generated_size to
report _generated_size instead of _generated_faces, while leaving the
surrounding generated-size update logic unchanged.
modules/worldscape_3d/worldscape_3d_mesh_asset.cpp-462-467 (1)

462-467: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

max_range truncates the float range to an int.

Line 463 assigns the result of a float CLAMP to an int. A range of 31.5 becomes 31, and the following CLAMP mixes int and real_t. Use real_t.

-	int max_range = CLAMP(_lod_ranges[1] - _lod_ranges[0], 0.f, 64.f);
+	real_t max_range = CLAMP(_lod_ranges[1] - _lod_ranges[0], 0.f, 64.f);

Line 170 has the same class of problem: _generated_faces is an int but the code assigns 2.f. Use 2.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_mesh_asset.cpp` around lines 462 - 467,
The set_fade_margin method currently truncates the clamped floating-point LOD
range by storing it in int max_range; change max_range to real_t so fractional
ranges are preserved throughout the fade-margin clamp. Also update the
_generated_faces initialization near line 170 to assign the integer literal 2
instead of 2.f.
modules/worldscape_3d/worldscape_3d_region.h-53-72 (1)

53-72: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The parallel constant arrays have no bounds enforcement at their use sites.

FORMAT, TYPESTR, and COLOR each hold 4 entries, indexed by MapType. WorldScape3DRegion::sanitize_map (worldscape_3d_region.cpp Lines 178-180) indexes all three with p_map_type and performs no range check.

sanitize_map is bound to script at Line 406 of the .cpp. VARIANT_ENUM_CAST does not validate the incoming value, so a script can pass any integer. Values of 4 or greater read past the end of all three arrays.

get_map and set_map both use a switch with a default branch, so only sanitize_map is exposed. Add the same range check there.

🛡️ Proposed guard for `worldscape_3d_region.cpp`
Ref<Image> WorldScape3DRegion::sanitize_map(const MapType p_map_type, const Ref<Image> &p_map) const {
	if (p_map_type < 0 || p_map_type >= TYPE_MAX) {
		LOG(ERROR, "Requested map type ", p_map_type, ", is invalid");
		return Ref<Image>();
	}
	...
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_region.h` around lines 53 - 72, Update
WorldScape3DRegion::sanitize_map to validate p_map_type before indexing FORMAT,
TYPESTR, or COLOR; reject values below zero or at least TYPE_MAX, log the
invalid map type, and return an empty Ref<Image>. Preserve the existing
sanitization flow for valid MapType values.
modules/worldscape_3d/worldscape_3d_material.cpp-882-882 (1)

882-882: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Save failures log the ERROR log level instead of the returned error code. Both messages pass the ERROR logging-level constant where the Error value from ResourceSaver::save belongs. Every save failure therefore reports the same meaningless number, and the accompanying advice to look up the @GlobalScope Error enum cannot be followed.

  • modules/worldscape_3d/worldscape_3d_material.cpp#L882: replace the ERROR argument with err.
  • modules/worldscape_3d/worldscape_3d_region.cpp#L300: replace the ERROR argument with err.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_material.cpp` at line 882, Replace the
logging-level constant with the returned save error variable err in both
save-failure messages: modules/worldscape_3d/worldscape_3d_material.cpp:882 and
modules/worldscape_3d/worldscape_3d_region.cpp:300. Keep the existing
ResourceSaver failure handling and message structure unchanged.
modules/worldscape_3d/worldscape_3d_mesher.cpp-79-79 (1)

79-79: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The grid-type labels in the log are swapped.

The parameter is p_standard_grid. When it is true, the message prints "symmetric". When it is false, it prints "standard". The two labels are reversed relative to the parameter name and to the MeshType names (STANDARD_TILE, STANDARD_EDGE_A).

🐛 Proposed fix
-	LOG(DEBUG, "Generating vertices and indices for a", p_standard_grid ? " symmetric " : " standard ", "grid mesh of width: ", p_size.x, " and height: ", p_size.y);
+	LOG(DEBUG, "Generating vertices and indices for a", p_standard_grid ? " standard " : " symmetric ", "grid mesh of width: ", p_size.x, " and height: ", p_size.y);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_mesher.cpp` at line 79, Correct the
conditional labels in the vertex/index generation log so p_standard_grid true
reports “standard” and false reports “symmetric,” matching the parameter and
MeshType naming. Update only the ternary expression in the LOG call.
modules/worldscape_3d/worldscape_3d_collision.cpp-409-412 (1)

409-412: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the shape id, not the grid index, in the diagnostic lookup.

i is the grid cell index. The shape id for that cell is grid[i]. _shape_get_position(i) therefore reports the position of an unrelated shape.

_shape_get_position also indexes _shapes[p_shape_id] without a bounds check. Today grid.size() equals the shape count because both derive from the same grid_width formula. If that ever diverges, this line becomes an out-of-range read.

🐛 Proposed fix
 			if (!p_rebuild && grid[i] >= 0) {
-				[[maybe_unused]] Vector2i center_pos = v3v2i(_shape_get_position(i));
+				[[maybe_unused]] Vector2i center_pos = v3v2i(_shape_get_position(grid[i]));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_collision.cpp` around lines 409 - 412,
Update the diagnostic lookup in the !p_rebuild branch to pass grid[i], the
active shape id, to _shape_get_position instead of i, the grid cell index; keep
the existing center_pos logging and continuation behavior unchanged.
modules/worldscape_3d/worldscape_3d_instancer.cpp-807-808 (1)

807-808: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the multimesh reference before use.

add_multimesh is bound to script at Line 1248. p_multimesh is dereferenced at Line 808 with no validity test. A script call that passes null dereferences a null pointer.

🛡️ Proposed guard
 void WorldScape3DInstancer::add_multimesh(const int p_mesh_id, const Ref<MultiMesh> &p_multimesh, const Transform3D &p_xform, const bool p_update) {
+	if (p_multimesh.is_null()) {
+		LOG(ERROR, "Provided multimesh is null. Doing nothing.");
+		return;
+	}
 	LOG(INFO, "Extracting ", p_multimesh->get_instance_count(), " transforms from multimesh");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_instancer.cpp` around lines 807 - 808,
Update WorldScape3DInstancer::add_multimesh to validate p_multimesh before
calling get_instance_count or otherwise dereferencing it. If the Ref<MultiMesh>
is null or invalid, return immediately using the surrounding code’s established
invalid-reference handling.
modules/worldscape_3d/worldscape_3d_instancer.cpp-568-577 (1)

568-577: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

get_mesh_asset results are dereferenced without a validity test. Both sites validate only that the mesh id is inside the range 0 to get_mesh_count() - 1. That check does not guarantee the slot holds an asset, so get_mesh_asset can still return a null Ref. _update_mmis (Line 85) and _create_multimesh (Line 422) both test the returned reference before use.

  • modules/worldscape_3d/worldscape_3d_instancer.cpp#L568-L577: test mesh_asset.is_null() and return before the get_density() call at Line 575.
  • modules/worldscape_3d/worldscape_3d_instancer.cpp#L764-L765: test mesh_asset.is_null() and continue before the get_height_offset() call; the modifier_shift branch iterates every mesh id, so empty slots are reachable.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_instancer.cpp` around lines 568 - 577,
Add validity checks after get_mesh_asset in both _update_mmis
(modules/worldscape_3d/worldscape_3d_instancer.cpp:568-577) and
_create_multimesh (modules/worldscape_3d/worldscape_3d_instancer.cpp:764-765).
Return immediately from _update_mmis when mesh_asset is null, and continue from
_create_multimesh before get_height_offset; preserve the existing mesh-id range
checks.
modules/worldscape_3d/worldscape_3d_instancer.cpp-1218-1226 (1)

1218-1226: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the initialization guard to dump_mmis.

dump_mmis is bound to script at Line 1256. Line 1225 calls _terrain->get_mmi_parent()->print_tree() with no check on _terrain. dump_data (Line 1184) guards with IS_DATA_INIT_MESG. Calling dump_mmis before initialization dereferences a null pointer.

🛡️ Proposed guard
 void WorldScape3DInstancer::dump_mmis() {
+	IS_DATA_INIT_MESG("Instancer isn't initialized.", VOID);
 	LOG(WARN, "Dumping MMI tree and node containers");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_instancer.cpp` around lines 1218 - 1226,
Add the same IS_DATA_INIT_MESG initialization guard used by dump_data to
WorldScape3DInstancer::dump_mmis before accessing _terrain or calling
get_mmi_parent()->print_tree(). Return immediately when initialization is
incomplete, while preserving the existing dump behavior after initialization.
modules/worldscape_3d/worldscape_3d_region.cpp-212-225 (1)

212-225: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check p_map before dereferencing, and test both bounds on one axis.

validate_map_size is bound to script at Line 407. Line 213 calls p_map->get_size() with no validity test. A script call with null dereferences a null pointer. sanitize_map is the only internal caller and it guards, so the gap is only reachable from script.

Line 222 also mixes axes: it compares region_sizev.x against the lower bound and region_sizev.y against the upper bound. The equality check at Line 214 makes this equivalent today. If that check is ever relaxed, a 32 by 4096 image passes both comparisons.

🛡️ Proposed fix
 bool WorldScape3DRegion::validate_map_size(const Ref<Image> &p_map) const {
+	if (p_map.is_null()) {
+		LOG(ERROR, "Provided map is null");
+		return false;
+	}
 	Vector2i region_sizev = p_map->get_size();
@@
-	if (region_sizev.x < 64 || region_sizev.y > 2048) {
+	if (region_sizev.x < 64 || region_sizev.x > 2048) {
 		LOG(ERROR, "Image size out of bounds (64-2048): ", region_sizev);
 		return false;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_region.cpp` around lines 212 - 225,
Update WorldScape3DRegion::validate_map_size to return false before calling
get_size when p_map is invalid or null, protecting the script-bound entry point.
Apply both the lower and upper size bounds to the same axis (preferably
region_sizev.x), while preserving the square and power-of-two validation.
modules/worldscape_3d/worldscape_3d_region.cpp-119-122 (1)

119-122: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The error message states the wrong expected count.

The condition requires exactly TYPE_MAX maps, which is 3. The message prints TYPE_MAX - 1, which is 2. A caller that follows the message supplies 2 maps and the call fails again.

🐛 Proposed fix
 	if (p_maps.size() != TYPE_MAX) {
-		LOG(ERROR, "Expected ", TYPE_MAX - 1, " maps. Received ", p_maps.size());
+		LOG(ERROR, "Expected ", TYPE_MAX, " maps. Received ", p_maps.size());
 		return;
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_region.cpp` around lines 119 - 122,
Correct the error message in the p_maps validation block to report TYPE_MAX as
the expected map count, matching the condition’s requirement for exactly
TYPE_MAX maps; leave the validation behavior unchanged.
modules/worldscape_3d/worldscape_3d_data.cpp-796-799 (1)

796-799: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check the material reference before dereferencing it.

_terrain is checked, but t_material is not. Ref<>::operator-> on a null reference dereferences a null pointer. WorldScape3D::_initialize instantiates the material only during initialization, and WorldScape3DMaterial::destroy can clear it, so a null material is reachable from a script call.

🛡️ Proposed guard
 	if (_terrain) {
 		Ref<WorldScape3DMaterial> t_material = _terrain->get_material();
-		bool auto_enabled = t_material->get_auto_shader();
+		if (t_material.is_null()) {
+			return Vector3(real_t(get_base(src)), real_t(get_overlay(src)), real_t(get_blend(src)) / 255.0f);
+		}
+		bool auto_enabled = t_material->get_auto_shader();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/worldscape_3d_data.cpp` around lines 796 - 799, In the
terrain material handling block, update the guard around _terrain and
get_material() so t_material is validated before calling get_auto_shader().
Preserve the existing behavior when both references are valid, and skip the
material-dependent logic when _terrain->get_material() returns a null Ref.
modules/worldscape_3d/editor/worldscape_3d_editor.cpp-881-886 (1)

881-886: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a validity check before you dereference the region.

Line 883 calls region->set_edited(false) with no validity check. Every other loop over region arrays in this file checks is_valid() first, for example line 581 and line 727. Keep the handling consistent so a null entry cannot crash the editor.

🛡️ Proposed fix
 		for (int i = 0; i < _edited_regions.size(); i++) {
 			Ref<WorldScape3DRegion> region = _edited_regions[i];
+			if (region.is_null()) {
+				continue;
+			}
 			region->set_edited(false);
 			// Make duplicate for redo backup
 			_edited_regions[i] = region->duplicate(true);
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/worldscape_3d_editor.cpp` around lines 881 -
886, In the loop over _edited_regions, validate each Ref<WorldScape3DRegion>
with is_valid() before calling set_edited(false) or duplicating it. Skip invalid
entries while preserving the existing handling for valid regions.
modules/worldscape_3d/editor/ui/double_slider.cpp-91-103 (1)

91-103: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Replace the _position_x == 0 sentinel.

Line 94 uses _position_x == 0 to detect the first call. If the label's x position is legitimately 0, _position_x stays 0 on every call and the else branch never runs, so the label position is never adjusted. The test also compares a real_t to 0 exactly.

Use an explicit boolean flag instead.

🐛 Proposed fix

Add a member to double_slider.h:

 	real_t _position_x = 0.f;
+	bool _position_x_captured = false;

Then update the check:

-		if (_position_x == 0) {
+		if (!_position_x_captured) {
 			_position_x = _label->get_position().x;
+			_position_x_captured = true;
 		} else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/double_slider.cpp` around lines 91 - 103,
Replace the `_position_x == 0` first-call sentinel in
`DoubleSlider::update_label` with an explicit boolean member declared in
`double_slider.h`. Initialize the flag as not yet captured, store the label’s
initial x position only on the first update, then adjust the label position on
subsequent calls regardless of whether that initial position is zero.
modules/worldscape_3d/editor/worldscape_3d_editor.cpp-846-854 (1)

846-854: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scale the movement average by the actual history size.

Line 854 always divides by 8. At the start of a stroke the history holds fewer than 8 entries, so _operation_movement is scaled down proportionally. _operation_movement drives dynamic_angle on line 408 and the gradient direction on line 348, so the first strokes use a direction vector of the wrong magnitude.

Divide by the number of accumulated entries instead.

🐛 Proposed fix
 	// size -1, dont add the last appended entry
 	for (int i = 0; i < _operation_movement_history.size() - 1; i++) {
 		_operation_movement += _operation_movement_history[i];
 	}
-	_operation_movement *= 0.125f; // 1/8th
+	_operation_movement /= real_t(_operation_movement_history.size());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/worldscape_3d_editor.cpp` around lines 846 -
854, Update the averaging logic after the _operation_movement_history loop to
scale _operation_movement by the actual number of accumulated history entries
rather than the fixed 0.125f factor. Use the same count represented by the loop
(excluding the last appended entry), while preserving the existing history
accumulation behavior.
modules/worldscape_3d/editor/ui/multi_picker.cpp-136-146 (1)

136-146: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a valid checked icon and make init() idempotent.

  • get_editor_theme_icon() is inherited from Control, and ColorPick is a valid EditorIcons name. However, picker_checked has no definition, so selected buttons receive a null icon. Use an existing icon or define picker_checked.
  • NOTIFICATION_POST_ENTER_TREE can call init() after each re-entry. Guard initialization using the created buttons or a dedicated flag. Do not use _points.is_empty(), because set_points() can populate _points before init() runs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/multi_picker.cpp` around lines 136 - 146,
Update MultiPicker::init() to use a valid editor theme icon for
_icon_picker_checked, or add the corresponding picker_checked icon definition.
Make init() idempotent by returning when the button collection or dedicated
initialization flag indicates buttons were already created; do not use
_points.is_empty(), since set_points() may populate it before initialization.
modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp-623-626 (1)

623-626: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

_clearing_resource is never assigned.

The member is declared at worldscape_3d_asset_dock.h Line 147 and initialized to false. No code path writes to it. This guard is therefore always false and has no effect. Remove it, or assign it around the clear operation if the guard was intended to suppress re-entrancy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp` around lines
623 - 626, Update ListContainer::on_resource_changed and the resource-clearing
flow to address the unused _clearing_resource guard: either remove the guard and
its member when suppression is unnecessary, or set and reset _clearing_resource
around the clear operation so the null-resource early return actually prevents
re-entrant handling.
modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp-381-387 (1)

381-387: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the no-op branch on the region map.

Line 385 assigns r_map.get(index) back to the same index. The statement has no effect. The trailing // ? comment indicates the intent was not established during the port.

Either delete the else branch or implement the intended behaviour.

♻️ Proposed cleanup
 			if (terrain->get_material()->get_world_background() == WorldScape3DMaterial::WorldBackground::NONE) {
 				if (r_map.get(index) == 0 && _active_operation == WorldScape3DEditor::Operation::ADD) {
 					r_map.set(index, -index - 1);
-				} else {
-					r_map.set(index, r_map.get(index)); // ?
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/worldscape_3d_ui.cpp` around lines 381 - 387,
Remove the no-op else branch in the region-map handling within the terrain
material background check, leaving the existing ADD operation that sets empty
entries to -index - 1 unchanged. Do not retain the self-assignment or its
trailing comment.
modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp-531-552 (1)

531-552: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The texture branch lacks the selection clamp that the mesh branch has.

Lines 549-551 clamp _selected_id for meshes after the rebuild. The texture branch has no equivalent. If textures are removed, _selected_id can exceed the new texture_count, and _brush_data["asset_id"] at Line 914 of worldscape_3d_ui.cpp then carries an out-of-range texture ID into the paint operation.

🛡️ Proposed fix
 		if (texture_count < WorldScape3DAssets::MAX_TEXTURES) {
 			add_item();
 		}
+		if (_selected_id >= texture_count || _selected_id < 0) {
+			set_selected_id(0);
+		}
 	} else {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp` around lines
531 - 552, Update the TYPE_TEXTURE branch in the asset rebuild logic to clamp
_selected_id after repopulating textures, matching the mesh branch’s behavior:
reset it to 0 when it is greater than or equal to texture_count or negative,
while leaving valid selections unchanged.
modules/worldscape_3d/editor/menu.cpp-52-60 (1)

52-60: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use TTR for the menu item labels.

Line 48 wraps the button text with TTR. The popup item labels stay untranslated. Editor-facing strings must go through TTR so they appear in the editor translation catalog.

🌐 Proposed fix
 	PopupMenu *p = get_popup();
-	p->add_item("Directory Setup...", MENU_DIRECTORY_SETUP);
-	p->add_item("Pack Textures...", MENU_PACK_TEXTURES); // TODO channel packer
+	p->add_item(TTR("Directory Setup..."), MENU_DIRECTORY_SETUP);
+	p->add_item(TTR("Pack Textures..."), MENU_PACK_TEXTURES); // TODO channel packer
 	p->add_separator("", MENU_SEPARATOR);
-	p->add_item("Bake ArrayMesh...", MENU_BAKE_ARRAY_MESH);
-	p->add_item("Bake Occluder3D...", MENU_BAKE_OCCLUDER);
+	p->add_item(TTR("Bake ArrayMesh..."), MENU_BAKE_ARRAY_MESH);
+	p->add_item(TTR("Bake Occluder3D..."), MENU_BAKE_OCCLUDER);
 	p->add_separator("", MENU_SEPARATOR2);
-	p->add_item("Set up Navigation...", MENU_SET_UP_NAVIGATION);
-	p->add_item("Bake NavMesh...", MENU_BAKE_NAV_MESH);
+	p->add_item(TTR("Set up Navigation..."), MENU_SET_UP_NAVIGATION);
+	p->add_item(TTR("Bake NavMesh..."), MENU_BAKE_NAV_MESH);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/menu.cpp` around lines 52 - 60, Update the popup
menu item labels in the menu initialization code around get_popup() to wrap
every editor-facing string passed to add_item, including “Directory Setup...”,
“Pack Textures...”, both bake labels, and the navigation labels, with TTR; leave
separators and menu IDs unchanged.
modules/worldscape_3d/editor/menu/channel_packer.cpp-529-540 (1)

529-540: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

_vbox bypasses _margin, so the margin container has no effect.

Line 535 adds _margin to the dialog. Line 540 adds _vbox to the dialog as a sibling instead of a child of _margin. The only child _margin ever receives is _save_file_dialog at Line 611. The margin constants at Lines 531-534 therefore never apply to the dialog content. Those constant names also carry the incorrect theme_override_constants/ prefix.

🐛 Proposed fix
-	_margin->add_theme_constant_override("theme_override_constants/margin_left", 5);
-	_margin->add_theme_constant_override("theme_override_constants/margin_top", 5);
-	_margin->add_theme_constant_override("theme_override_constants/margin_right", 5);
-	_margin->add_theme_constant_override("theme_override_constants/margin_bottom", 5);
+	_margin->add_theme_constant_override("margin_left", 5);
+	_margin->add_theme_constant_override("margin_top", 5);
+	_margin->add_theme_constant_override("margin_right", 5);
+	_margin->add_theme_constant_override("margin_bottom", 5);
 	add_child(_margin);
 
 	_vbox = memnew(VBoxContainer);
 	_vbox->set_v_size_flags(Control::SIZE_EXPAND_FILL);
 	_vbox->set_alignment(BoxContainer::ALIGNMENT_CENTER);
-	add_child(_vbox);
+	_margin->add_child(_vbox);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/menu/channel_packer.cpp` around lines 529 - 540,
Make _vbox a child of _margin instead of adding it as a sibling to the dialog,
so the dialog content is enclosed by the margin container. Update the four
margin override keys in the initialization near _margin to use the correct
theme-constant property names without the theme_override_constants/ prefix.
modules/worldscape_3d/editor/menu/channel_packer.cpp-623-632 (1)

623-632: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the generated height image against a null Ref and tiny source images.

Line 624 dereferences the return value of WorldScape3DUtil::luminance_to_height() without a null check. Line 631 computes w / 4 and h / 4 with integer division. For an albedo smaller than 4 pixels in either dimension, the resize target becomes 0 and Image::resize fails.

🛡️ Proposed fix
 	auto height_texture = WorldScape3DUtil::luminance_to_height(_albedo_panel->get_image());
-	if (height_texture->is_empty()) {
+	if (height_texture.is_null() || height_texture->is_empty()) {
 		EditorNode::get_singleton()->show_warning("Height Texture Generation error");
 		return;
 	}
 	// blur the image by resizing down and back...
 	int w = height_texture->get_width();
 	int h = height_texture->get_height();
-	height_texture->resize(w / 4, h / 4);
+	height_texture->resize(MAX(1, w / 4), MAX(1, h / 4));
 	height_texture->resize(w, h, Image::INTERPOLATE_CUBIC);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/menu/channel_packer.cpp` around lines 623 - 632,
Update the height texture generation flow around
WorldScape3DUtil::luminance_to_height to return with the existing warning when
the returned Ref is null or empty before dereferencing it. Ensure the downscale
dimensions in the subsequent resize calls are clamped to at least 1 pixel per
axis so images smaller than 4 pixels remain valid.
modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp-664-668 (1)

664-668: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid mutating the static unit strings in init(). String(const char *) treats each literal byte as Latin-1, so substr(1, 1) relies on UTF-8 encoding. On the next NOTIFICATION_POST_ENTER_TREE, it runs on the one-character result and stores an empty string. Use String::chr(0x00B0) and String::chr(0x00B1) with non-static constants.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp` around lines 664 -
668, Update the unit-string initialization in init() to use non-static constants
constructed with String::chr(0x00B0) and String::chr(0x00B1). Remove the
substr-based mutation so repeated NOTIFICATION_POST_ENTER_TREE calls preserve
both degree and plus-minus symbols.

Comment thread modules/worldscape_3d/editor/gizmos.cpp
Comment thread modules/worldscape_3d/editor/menu/baker.cpp
Comment thread modules/worldscape_3d/editor/menu/baker.cpp
Comment thread modules/worldscape_3d/editor/menu/baker.cpp
Comment thread modules/worldscape_3d/editor/menu/channel_packer.cpp
Comment thread modules/worldscape_3d/generated_texture.cpp
Comment thread modules/worldscape_3d/worldscape_3d_material.cpp
Comment thread modules/worldscape_3d/worldscape_3d_mesh_asset.cpp
Comment thread modules/worldscape_3d/worldscape_3d_mesh_asset.cpp
Comment thread modules/worldscape_3d/worldscape_3d.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/worldscape_3d/editor/worldscape_3d_editor.cpp`:
- Around line 1415-1417: Update the setting key checked in
setup_editor_settings() to match the initialized and read key
"terrain_editor/config/alt_key_bind", so an existing alternate-key preference is
preserved and only a missing setting is initialized to 0.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b6fe0ed8-9172-4701-be54-ef02b9429c5a

📥 Commits

Reviewing files that changed from the base of the PR and between d00628b and 89617a2.

📒 Files selected for processing (3)
  • modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp
  • modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp
  • modules/worldscape_3d/editor/worldscape_3d_editor.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • modules/worldscape_3d/editor/ui/worldscape_3d_tools.cpp
  • modules/worldscape_3d/editor/ui/worldscape_3d_asset_dock.cpp

Comment thread modules/worldscape_3d/editor/worldscape_3d_editor.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔇 Additional comments (2)
modules/worldscape_3d/editor/gizmos.cpp (1)

63-65: Preserve the reset path for every null editor.

The new guard prevents the dereference, but it returns without clearing _show_rect, _region_size, or _grid when position is non-zero and editor is null. The first condition does not run in that case, so previous gizmo geometry can remain visible. Move the !editor check into the existing reset branch or call the reset path before returning.

modules/worldscape_3d/worldscape_3d_data.cpp (1)

161-162: 🗄️ Data Integrity & Integration

⚠️ Unverified finding
Sandbox verification was unavailable.

Verify the complete 4096 region-size contract.

This check accepts 4096, but change_region_size() later passes the value to _terrain->set_region_size((WorldScape3D::RegionSize)p_new_size) at Line 214. If the enum, setter validation, or resource loader still supports only 64 through 2048, this function can remove the old regions and create 4096-sized replacements before the setter rejects the value. Confirm that enum values, setters, load/save paths, editor hints, and documentation all accept 4096.

Verification script

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5407be99-f3da-404b-b7b4-25608430b413

📥 Commits

Reviewing files that changed from the base of the PR and between 97fb252 and a3e1ec1.

📒 Files selected for processing (3)
  • modules/worldscape_3d/editor/gizmos.cpp
  • modules/worldscape_3d/worldscape_3d.cpp
  • modules/worldscape_3d/worldscape_3d_data.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • modules/worldscape_3d/worldscape_3d.cpp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant