Skip to content

Conversation

@JiepengTan
Copy link
Contributor

related pr: goplus/godot#184

@JiepengTan JiepengTan marked this pull request as draft October 24, 2025 09:37
@gemini-code-assist
Copy link

Summary of Changes

Hello @JiepengTan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the engine's coordinate system management by migrating Y-axis inversion logic from C++ to Go. It establishes a dedicated adapter layer that transparently handles coordinate translations between the SPX (Y-up) and Godot (Y-down) systems across various core manager functionalities. This change centralizes a critical transformation, promoting consistency and simplifying future development efforts related to coordinate handling.

Highlights

  • Centralized Y-axis Inversion: A new file, internal/enginewrap/coord_adapter.go, has been added to centralize the Y-axis coordinate conversion logic, specifically for inverting the Y-coordinate, to bridge the difference between SPX (Y-up) and Godot (Y-down) coordinate systems.
  • Manager Method Overrides: The new adapter file introduces overrides for numerous methods across SpriteMgr, SceneMgr, PhysicMgr, InputMgr, DebugMgr, TilemapMgr, and NavigationMgr. These overrides automatically apply the flipY transformation to relevant Vec2 parameters and return values, ensuring consistent coordinate handling.
  • Specific Raycast Handling: The physic.go file has been updated to correctly apply Y-axis inversion when converting rayCastResult data to and from arrays. This ensures that raycast positions and normals are consistently translated between the SPX and Godot coordinate systems.
  • Pathfinding Coordinate Conversion: The NavigationMgrImpl.FindPath method now includes explicit iteration and Y-coordinate flipping for its array-based path results, ensuring that the returned path aligns with the SPX coordinate system after conversion from Godot.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the coordinate system conversion logic by moving it from C++ to Go, which is a positive architectural change. The new coord_adapter.go file centralizes the Y-axis inversion needed to align SPX's coordinate system with Godot's. My review includes a critical fix for a potential panic in the new adapter code due to an unsafe type assertion. I've also included a few medium-severity suggestions to improve the clarity and consistency of comments related to these coordinate conversions.

Comment on lines +156 to +297
// This conversion should be handled at upper layer due to gdx.Array access limitations
func (pself *NavigationMgrImpl) FindPath(from Vec2, to Vec2, withJump bool) gdx.Array {
var arr = pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
result := arr.([]float32)
if result == nil {
return nil
}
// flipY to convert godot coord to spx coord
for i := 0; i+1 < len(result); i += 2 {
result[i+1] = -result[i+1]
}
return result
}

Choose a reason for hiding this comment

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

critical

The type assertion arr.([]float32) on line 159 is unsafe and will cause a panic if arr is nil or not of type []float32. You should use the "comma ok" idiom to safely perform the type assertion. Additionally, the comment on line 156 is now misleading since the conversion is handled within this function, and should be removed or updated.

Suggested change
// This conversion should be handled at upper layer due to gdx.Array access limitations
func (pself *NavigationMgrImpl) FindPath(from Vec2, to Vec2, withJump bool) gdx.Array {
var arr = pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
result := arr.([]float32)
if result == nil {
return nil
}
// flipY to convert godot coord to spx coord
for i := 0; i+1 < len(result); i += 2 {
result[i+1] = -result[i+1]
}
return result
}
// The returned path coordinates are converted to the SPX coordinate system.
func (pself *NavigationMgrImpl) FindPath(from Vec2, to Vec2, withJump bool) gdx.Array {
arr := pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
if arr == nil {
return nil
}
result, ok := arr.([]float32)
if !ok {
// Consider logging an error here as this is an unexpected type.
return arr
}
// flipY to convert godot coord to spx coord
for i := 0; i+1 < len(result); i += 2 {
result[i+1] = -result[i+1]
}
return result
}

Comment on lines +88 to +90
ary[3] = -engine.ConvertToInt64(p.PosY) // here need flipY to convert spx coord to godot coord
ary[4] = engine.ConvertToInt64(p.NormalX)
ary[5] = engine.ConvertToInt64(p.NormalY)
ary[5] = -engine.ConvertToInt64(p.NormalY)

Choose a reason for hiding this comment

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

medium

The comment on line 88 is a bit informal. Also, NormalY on line 90 is also flipped but lacks a comment. For better clarity and maintainability, it's good practice to use consistent and descriptive comments for coordinate system conversions.

Suggested change
ary[3] = -engine.ConvertToInt64(p.PosY) // here need flipY to convert spx coord to godot coord
ary[4] = engine.ConvertToInt64(p.NormalX)
ary[5] = engine.ConvertToInt64(p.NormalY)
ary[5] = -engine.ConvertToInt64(p.NormalY)
ary[3] = -engine.ConvertToInt64(p.PosY) // Flip Y for Godot's coordinate system (Y-down).
ary[4] = engine.ConvertToInt64(p.NormalX)
ary[5] = -engine.ConvertToInt64(p.NormalY) // Flip Y for Godot's coordinate system (Y-down).

Comment on lines +106 to +108
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // here need flipY to convert to godot coord to spx coord
p.NormalX = engine.ConvertToFloat64(dataAry[4])
p.NormalY = engine.ConvertToFloat64(dataAry[5])
p.NormalY = -engine.ConvertToFloat64(dataAry[5])

Choose a reason for hiding this comment

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

medium

The comment on line 106 has a typo ("to godot coord to spx coord") and is a bit informal. The same conversion is done for NormalY on line 108. Using a clear and consistent comment style for these conversions will make the code easier to understand.

Suggested change
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // here need flipY to convert to godot coord to spx coord
p.NormalX = engine.ConvertToFloat64(dataAry[4])
p.NormalY = engine.ConvertToFloat64(dataAry[5])
p.NormalY = -engine.ConvertToFloat64(dataAry[5])
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // Flip Y to convert from Godot (Y-down) to SPX (Y-up).
p.NormalX = engine.ConvertToFloat64(dataAry[4])
p.NormalY = -engine.ConvertToFloat64(dataAry[5]) // Flip Y to convert from Godot (Y-down) to SPX (Y-up).

@xgopilot
Copy link
Contributor

xgopilot bot commented Oct 24, 2025

Code Review Summary

Excellent refactoring that centralizes coordinate conversion logic. The approach is architecturally sound with good separation of concerns.

Critical issues:

  • Type assertion panic risk in FindPath (coord_adapter.go:159) - must fix
  • Misleading/incorrect documentation comments - should fix

Positives:

  • Clean design with dedicated coord_adapter.go file
  • Comprehensive coverage across all managers
  • Clear file-level documentation of Y-axis inversion

Performance impact is negligible.

// This conversion should be handled at upper layer due to gdx.Array access limitations
func (pself *NavigationMgrImpl) FindPath(from Vec2, to Vec2, withJump bool) gdx.Array {
var arr = pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
result := arr.([]float32)
Copy link
Contributor

Choose a reason for hiding this comment

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

<!-- codeagent-review-id: pr-973 -->

Critical: Type assertion panic risk

This unsafe type assertion will panic if arr is not []float32, creating a denial-of-service vulnerability.

Suggested change
result := arr.([]float32)
result, ok := arr.([]float32)
if \!ok || result == nil {

// ============================================================================

// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// This conversion should be handled at upper layer due to gdx.Array access limitations
Copy link
Contributor

Choose a reason for hiding this comment

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

Documentation inaccuracy

This comment states "conversion should be handled at upper layer" but the conversion IS done here (lines 163-165). Unlike RaycastWithDetails, this function actually converts the array elements.

Suggested change
// This conversion should be handled at upper layer due to gdx.Array access limitations
// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// Input coordinates are converted via flipY, and output array Y values are also
// converted here by negating all odd-indexed elements (y-coordinates).

return nil
}
// flipY to convert godot coord to spx coord
for i := 0; i+1 < len(result); i += 2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistent approach + minor optimization

This inline Y-negation is inconsistent with the flipY() helper used everywhere else.

Also, simplify the loop to start at index 1 to avoid the addition in the condition:

Suggested change
for i := 0; i+1 < len(result); i += 2 {
for i := 1; i < len(result); i += 2 {
result[i] = -result[i]
}

}

// RaycastWithDetails returns array: [collide, sprite_gid, pos.x, pos.y, normal.x, normal.y]
// This conversion should be handled at upper layer due to gdx.Array access limitations
Copy link
Contributor

Choose a reason for hiding this comment

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

Misleading documentation

The comment is confusing. Input coordinates ARE converted here via flipY(), but output array Y values are converted in physic.go:tryRaycastResult(). Clarify what's happening where:

Suggested change
// This conversion should be handled at upper layer due to gdx.Array access limitations
// RaycastWithDetails returns array: [collide, sprite_gid, pos.x, pos.y, normal.x, normal.y]
// Input coordinates are converted here via flipY, but output array pos/normal Y values
// must be converted at the upper layer (see physic.go:tryRaycastResult) due to gdx.Array
// access limitations preventing direct element modification.

p.SpriteId = dataAry[1]
p.PosX = engine.ConvertToFloat64(dataAry[2])
p.PosY = engine.ConvertToFloat64(dataAry[3])
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // here need flipY to convert to godot coord to spx coord
Copy link
Contributor

Choose a reason for hiding this comment

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

Grammar error in comment

Suggested change
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // here need flipY to convert to godot coord to spx coord
p.PosY = -engine.ConvertToFloat64(dataAry[3]) // flipY to convert from Godot coord to SPX coord

p.PosY = -engine.ConvertToFloat64(dataAry[3]) // here need flipY to convert to godot coord to spx coord
p.NormalX = engine.ConvertToFloat64(dataAry[4])
p.NormalY = engine.ConvertToFloat64(dataAry[5])
p.NormalY = -engine.ConvertToFloat64(dataAry[5])
Copy link
Contributor

Choose a reason for hiding this comment

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

Same grammar fix needed

Suggested change
p.NormalY = -engine.ConvertToFloat64(dataAry[5])
p.NormalY = -engine.ConvertToFloat64(dataAry[5]) // flipY to convert from Godot coord to SPX coord

@JiepengTan JiepengTan force-pushed the pr_move_coord_convert_to_go_invery branch from 08672e3 to 274d417 Compare October 24, 2025 09:56
@JiepengTan JiepengTan force-pushed the pr_move_coord_convert_to_go_invery branch from 274d417 to 6e752e4 Compare December 17, 2025 10:13
@JiepengTan JiepengTan force-pushed the pr_move_coord_convert_to_go_invery branch from 6358b96 to a63b98e Compare December 17, 2025 15:21
@JiepengTan JiepengTan requested a review from joeykchen December 17, 2025 15:23
@JiepengTan JiepengTan marked this pull request as ready for review December 17, 2025 15:23
@JiepengTan
Copy link
Contributor Author

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively centralizes the coordinate system conversion logic by introducing an adapter layer that inverts the Y-axis. This is a good architectural improvement. The implementation is mostly solid, but I've found a critical issue in FindPath that could lead to a panic due to an unsafe type assertion. I've also provided a couple of medium-severity suggestions to improve code consistency and maintainability by refactoring RaycastWithDetails to be more consistent with FindPath and updating a misleading comment.

Comment on lines +287 to +291
var arr = pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
result := arr.([]float32)
if result == nil {
return nil
}

Choose a reason for hiding this comment

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

critical

The type assertion arr.([]float32) is unsafe and will cause a panic if arr is nil or has a different underlying type. This can lead to a crash. Please use the two-value type assertion to handle these cases gracefully.

	arr := pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
	if arr == nil {
		return nil
	}
	result, ok := arr.([]float32)
	if !ok || result == nil {
		return nil
	}

Comment on lines +189 to +191
func (pself *PhysicMgrImpl) RaycastWithDetails(from Vec2, to Vec2, ignoreSprites gdx.Array, collisionMask int64, collideWithAreas bool, collideWithBodies bool) gdx.Array {
return pself.physicMgrImpl.RaycastWithDetails(flipY(from), flipY(to), ignoreSprites, collisionMask, collideWithAreas, collideWithBodies)
}

Choose a reason for hiding this comment

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

medium

For consistency with FindPath and to better centralize the coordinate conversion logic, consider handling the Y-coordinate flipping for the returned array within this function. This would make the adapter layer fully responsible for coordinate translations. This change would also require removing the manual Y-flipping from the ToArray and tryRaycastResult functions in physic.go, and updating the comments on lines 187-188.

Suggested change
func (pself *PhysicMgrImpl) RaycastWithDetails(from Vec2, to Vec2, ignoreSprites gdx.Array, collisionMask int64, collideWithAreas bool, collideWithBodies bool) gdx.Array {
return pself.physicMgrImpl.RaycastWithDetails(flipY(from), flipY(to), ignoreSprites, collisionMask, collideWithAreas, collideWithBodies)
}
func (pself *PhysicMgrImpl) RaycastWithDetails(from Vec2, to Vec2, ignoreSprites gdx.Array, collisionMask int64, collideWithAreas bool, collideWithBodies bool) gdx.Array {
arr := pself.physicMgrImpl.RaycastWithDetails(flipY(from), flipY(to), ignoreSprites, collisionMask, collideWithAreas, collideWithBodies)
if arr == nil {
return nil
}
result, ok := arr.([]int64)
if !ok {
// This case is unexpected, returning the original array. Consider logging an error.
return arr
}
if len(result) >= 6 {
// result[3] is pos.y, result[5] is normal.y
// Convert from Godot's coordinate system (y-down) to SPX's (y-up).
result[3] = -result[3]
result[5] = -result[5]
}
return result
}

Comment on lines +284 to +285
// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// This conversion should be handled at upper layer due to gdx.Array access limitations

Choose a reason for hiding this comment

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

medium

This comment is misleading. The coordinate conversion is handled within this function, not at an upper layer. Please update the comment to accurately describe the function's behavior.

Suggested change
// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// This conversion should be handled at upper layer due to gdx.Array access limitations
// FindPath returns an array of path coordinates: [x0, y0, x1, y1, ...].
// The coordinates are converted from Godot's system (Y-down) to SPX's system (Y-up).

// This conversion should be handled at upper layer due to gdx.Array access limitations
func (pself *NavigationMgrImpl) FindPath(from Vec2, to Vec2, withJump bool) gdx.Array {
var arr = pself.navigationMgrImpl.FindPath(flipY(from), flipY(to), withJump)
result := arr.([]float32)
Copy link
Contributor

Choose a reason for hiding this comment

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

Security/Reliability Issue: Unchecked Type Assertion

This unchecked type assertion will panic if the underlying type is not []float32, causing a potential denial of service. Use the safe two-value form:

result, ok := arr.([]float32)
if !ok || result == nil {
    return nil
}

// ============================================================================

// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// This conversion should be handled at upper layer due to gdx.Array access limitations
Copy link
Contributor

Choose a reason for hiding this comment

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

Documentation Issue: Misleading Comment

The comment states "This conversion should be handled at upper layer" but the function actually DOES perform output array conversion in lines 293-294. The comment from RaycastWithDetails was likely copied but doesn't apply here. Consider:

// FindPath returns array: [x0, y0, x1, y1, x2, y2, ...]
// Both input and output coordinates are converted here.
// Output Y-coordinates (at odd indices) are inverted to convert from Godot to SPX coordinate system.

}

// RaycastWithDetails returns array: [collide, sprite_gid, pos.x, pos.y, normal.x, normal.y]
// This conversion should be handled at upper layer due to gdx.Array access limitations
Copy link
Contributor

Choose a reason for hiding this comment

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

Documentation Issue: Incomplete Comment

The comment is accurate that output conversion happens at the upper layer, but should clarify that only INPUT coordinates are converted here. The returned array's Y-coordinates (indices 3 and 5) must be handled by the caller:

// RaycastWithDetails returns array: [collide, sprite_gid, pos.x, pos.y, normal.x, normal.y]
// Note: Input coordinates (from, to) are converted here, but the returned array 
// Y-coordinates (pos.y at index 3, normal.y at index 5) must be converted by the 
// caller due to gdx.Array access limitations. See physic.go:tryRaycastResult().

)

// flipY flips the Y coordinate to convert between SPX and Godot coordinate systems
func flipY(pos Vec2) Vec2 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Performance Concern: Frequent Allocations in Hot Path

The flipY function creates a new Vec2 struct on every call. Since this is called for every position/velocity operation (in rendering and physics updates), this creates significant memory pressure. At 60 FPS with 100 sprites, this could generate ~15,000-25,000 allocations/sec (~6-8 KB/frame).

Consider optimizing for hot paths. One approach is to modify the copy in-place:

func flipY(pos Vec2) Vec2 {
    pos.Y = -pos.Y
    return pos
}

Or consider pointer-based APIs for frequently-called methods like GetPosition/SetPosition to avoid repeated allocations in Get-Modify-Set patterns (e.g., AddPos calls GetPosition then SetPosition).

pself.spriteMgrImpl.AddImpulse(obj, flipY(impulse))
}

func (pself *SpriteMgrImpl) GetGravity(obj gdx.Object) float64 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing Documentation

Gravity negation requires explanation. Unlike vectors, gravity is a scalar, so the sign flip is conceptually different. Add a comment:

// GetGravity returns gravity with inverted sign to match SPX's upward Y-axis convention.
// In SPX, positive Y is up, so positive gravity means upward force. In Godot, positive 
// Y is down, so positive gravity means downward force. The sign inversion maintains 
// consistent physical behavior across both coordinate systems.
func (pself *SpriteMgrImpl) GetGravity(obj gdx.Object) float64 {
    return -pself.spriteMgrImpl.GetGravity(obj)
}

@xgopilot
Copy link
Contributor

xgopilot bot commented Dec 17, 2025

Code Review Summary

Excellent architectural improvement moving coordinate conversions from C++ to Go! The adapter pattern provides clear separation of concerns. However, several noteworthy issues need attention:

Critical:

  • Unchecked type assertion in FindPath (security/reliability risk)
  • Performance: flipY creates allocations in hot paths (~15k-25k/sec with 100 sprites at 60 FPS)

Important:

  • Misleading documentation for FindPath and RaycastWithDetails
  • Missing explanation for gravity sign convention

Recommendations:

  1. Fix type assertion with safe two-value form
  2. Optimize flipY to reduce allocations
  3. Clarify documentation for array-based coordinate conversions

See inline comments for details.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant