Render the entrance arrow during ghost placement #61
Labels
No labels
assets
bug
duplicate
enhancement
help wanted
invalid
question
testing
ui
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
liamjd/UntitledColonyBuilder#61
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Split out of #55, which now covers only the data model:
BuildingFootprintparses an arrow (< > ^ v) into an anchor-relativedoorstep(steps)and a unitentrance_direction(steps), both rotating with the cells viarotate_offset().During ghost placement mode, render an arrow pointing at the building's entrance so the player can see which way the door faces while rotating.
Approach
MeshInstance3DtoPlacementControlleras a sibling of the ghost mesh, not a child. The ghost is positioned at the centre of its rotated bounds, so a child's offset would be in mesh-local space and coupled to the AABB. As a sibling it can be positioned directly from the grid, the same wayGhostMeshandGridPlacementMeshalready are.map.cell_to_world(anchor + footprint.doorstep(steps)), plus a small upward lift. Note thatTerrainGrid.cell_to_world()already returns the tile centre sitting on its top face (surface_y(level_at(cell)), globalised) — there is no separate surface-height lookup to do.@export, not the ghost'smesh_hover_height. That is 0.75, tuned for a floating building box; an arrow that high above a bare tile reads as detached rather than as a marker on the ground. The highlight quads sit at+0.02for comparison.rotation.yfromfootprint.entrance_direction(steps)rather than from the step count, so the arrow and the footprint cannot disagree.ymaps to worldz, and Godot's+Yrotation is counter-clockwise viewed from above — which is whyBuildingSite.rotation_for()negates. For a mesh pointing along-Zat yaw 0, the yaw isatan2(-float(dir.x), -float(dir.y)). Checks out on all four:(0,-1)-> 0,(1,0)-> -90 deg,(0,1)-> 180 deg,(-1,0)-> +90 deg.rotation_for()as a second static onBuildingSite. That class already exists to hold the pure site maths both the ghost and the placed building call, and a static is testable headless — which the rendering is not. It is the only part of this issue that can have real automated coverage.PrismMeshnor a zero-top-radiusCylinderMeshworks directly — both point along +Y, so an arrow lying flat in the ground plane would need a pitch as well as a yaw, androtation.x+rotation.yon one node compose in Godot's YXZ Euler order. That is the "plausible-but-wrong orientation" failure CLAUDE.md warns about. Two ways out, both keepingrotation.yas the only thing ever set:EntranceArrowcarries the yaw, aMeshInstance3Dchild carriesrotation_degrees = Vector3(-90, 0, 0). Authored asrotation_degrees, never a hand-computedTransform3D.ImmediateMeshtriangle/chevron in the XZ plane pointing along-Z, the way_rebuild_highlight()already generates its quads. More consistent with the surrounding code, and a flat chevron reads better than a cone under an orthographic top-down camera.Not what the first draft of this issue said
Two corrections, recorded so they are not re-derived:
"The existing cache check inThis misreads the code. The ghost's own position and rotation are set every frame, outside that block; the cache exists solely becausePlacementControlleralready includes_rotation_steps, so the arrow only needs updating when that check fires."_rebuild_highlight()regenerates anImmediateMeshfrom scratch. Setting aVector3and afloatper frame costs nothing — update the arrow unconditionally, next to the ghost. What should be built once, in_ready(), is the arrow mesh: unlike the ghost mesh it does not vary by building type, so it never needs rebuilding."Position ... seated on that tile's surface height plus the same hover lift the ghost uses."Both halves wrong, per the Position bullet above.Easy to miss
_clear_hover()must hide the arrow, and_cancel_placement()must clear it. Every early return in_physics_process()funnels through_clear_hover(), and its docstring already warns about exactly this class of bug.BuildingSite.position_for()bails onoffsets.is_empty()specifically so a failed parse does not push errors 60x/second. The arrow needs the same guard: on a shape that failed to parse,_doorstepis left atVector2i.MAXand the arrow would be flung to an absurd cell.Out of scope
PlacementRules, and its own issue. Note that until then a doorstep can fall outside the grid:level_at()clamps out-of-bounds cells to the datum level, so the arrow will render off the map edge rather than error.Verification
Headless renders nothing under gl_compatibility, so this needs a headed run with a screenshot for each of the four rotations of an asymmetric footprint such as
one_by_three_L.tools/screenshot_buildings.gdis the harness to copy — headedSceneTree, builds a plains map, waits for frames,save_png. Its header marks it THROWAWAY for #36, so write a sibling for this issue rather than editing it.The
BuildingSiteyaw static above should get unit tests in the normal GUT suite; it is the one piece here that does not need eyes on a screenshot.