Set E1.M4 documentation status to In Progress across decomposition tracking docs and add lower-bound slot validation coverage. Mark the corresponding review suggestions and nit as resolved in the saved review file.
Ship a server-owned hotbar loadout API with Postgres-or-memory persistence so slot bindings survive reconnects in configured environments. Wire client boot hydration and add tests/docs/manual QA to keep the prototype contract and policy explicit.
Capture the E1.M4 vertical slice backlog in-repo and sync module/dependency docs with the created Linear stories so planning and execution references stay aligned.
Map SelectionEvent to target_changed, reserve ability_cast_requested and ability_cast_denied hook locations, and align plan/manual QA/decomposition docs with TODO(E9.M1) telemetry follow-up.
Add selection_event signal when lockedTargetId changes; attribute tab, clear,
and server_correction from POST/GET paths. Optional log_selection_events print.
GdUnit coverage; README, E1.M3 snapshot, manual QA, and plan checklist updated.
Review 2026-04-21 caught that the hybrid soft-lock refresh path is
unreachable during normal WASD locomotion. `main.gd` was wiring
`TargetSelectionClient` to `PositionAuthorityClient.authoritative_position_received`,
which only fires on boot sync and move-rejection resync — successful
`move-stream` 200s are intentionally silent to avoid RTT rubber-banding.
So walking out of an alpha/beta radius never triggered the throttled
`GET /target` the HUD depends on, and validity stayed stuck at `ok`.
Add a separate `authoritative_ack(world)` signal on `PositionAuthorityClient`
emitted from both `BOOT_GET` 200 and successful `move-stream` 200, and
switch `TargetSelectionClient` (+ `main.gd`) to it. The snap signal keeps
its current semantics so rubber-band suppression is untouched; the ack
signal is a pure heartbeat that cooldown-throttled consumers can hook.
Also adds an integration test that wires both real clients together (per
the review's suggestion 1) so renames or mis-wires between the two
scripts fail loudly rather than silently skipping the end-to-end path.
- `client/scripts/position_authority_client.gd`: new `authoritative_ack`
signal; extracted `_parse_world_from_response` helper to share JSON
parse across `BOOT_GET` and `STREAM_POST`; emit ack from both.
- `client/scripts/target_selection_client.gd`: rename
`on_authoritative_position_snap` to `on_authoritative_ack`; drop
`apply_as_snap` gate (no longer meaningful).
- `client/scripts/main.gd`: connect the new signal.
- `client/test/position_authority_client_test.gd`: assert ack on boot
200 and stream 200.
- `client/test/target_selection_client_test.gd`: rename tests for new
handler.
- `client/test/target_refresh_on_locomotion_test.gd`: new integration
suite wiring real authority + target clients.
- Plan Decision 3 + Tests row + Files-to-modify row updated.
- `docs/reviews/2026-04-21-NEO-24.md`: committed with blocking issue +
suggestions struck through with `Done.` notes per
planning-implementation-docs rule.
- `docs/decomposition/modules/documentation_and_implementation_alignment.md`:
E1.M3 row now reflects NEO-24 landed.
- `client/README.md` + `docs/manual-qa/NEO-24.md`: refreshed signal names
so the soft-lock refresh description matches the wiring.
Tests: 88/88 GdUnit headless passed locally.
Introduce POST /game/players/{id}/move-stream with ordered targets, full-chain
validation before apply, and PositionStateResponse. Godot client uses camera-
relative WASD, queues samples, and snaps to stream responses; debug builds keep
click-to-move via ground_pick. Update READMEs, E1.M1 snapshot, and tests.
Migrate Jira NEON-* references to Linear NEO-* and linear.app links in
docs, Cursor rules, READMEs, and traceability comments. Renamed
implementation plans and dated reviews to NEO filenames.
Branch is based on origin/main with this commit only (no NEON-29
gameplay chain). client/README follows main’s district/map copy;
player.gd matches main aside from NEO-14 debug trace prefixes.
ground_pick.gd now checks _collider_is_occluder before the walkable
break: when the ray hits a body in the "occluder" group it advances
OCCLUDER_PICK_THROUGH past the hit point and continues, unconditionally
and regardless of OcclusionPolicy fade state. Non-occluder click
targeting is unchanged.
Three unit tests added to ground_pick_test.gd covering occluder ancestry
detection. Plan open questions and module doc updated.
Adds OcclusionPolicy resource and wires it into IsometricFollowCamera.
Each _process frame, iterative rays from the camera eye to the player
focus detect bodies tagged "occluder"; their MeshInstance3D surfaces are
overridden with a transparent StandardMaterial3D (fade_alpha=0.25, instant).
Materials restore when the body clears the ray. Null-material surfaces
(e.g. the prototype Obstacle with no material assigned) receive a plain
transparent StandardMaterial3D so they fade correctly.
- client/scripts/occlusion_policy.gd — new Resource (enabled, fade_alpha,
occluder_group, occluder_collision_mask, max_occluder_cast_depth,
occluder_count_log_threshold, is_valid)
- client/resources/isometric_occlusion_policy.tres — default instance
- client/scripts/isometric_follow_camera.gd — occlusion_policy export,
_update_occlusion pipeline, _apply_occluder_fade/_restore_occluder helpers,
static occlusion_policy_is_valid guard, _exit_tree cleanup
- client/scenes/main.tscn — Obstacle tagged "occluder", policy assigned
- client/test/occlusion_policy_test.gd — new GdUnit4 unit tests
- client/test/isometric_follow_camera_test.gd — policy guard static tests
- docs/decomposition/modules/E1_M2_IsometricCameraController.md — NEON-27
snapshot + readability demo gate note
- docs/plans/NEON-27-implementation-plan.md — null-material decision recorded
- Link NEON-10/NEON-25–28 under parent NEON-1 in Summary and Jira backlog table
- Document mid-project-only yaw orbit compromise; post-release camera model settled
- Extend CameraState contract and slice/risk notes accordingly
Module/slice grouping below epics is labels on stories only; Feature
work items are not used. Cross-link from jira-git-naming, story kickoff,
and documentation alignment table.
Align plan, client/server README, E1.M1 snapshot, and code review note with
the chosen design: descend bypass favors smooth stepped surfaces; multi-click
around obstacles is OK. NS-24 remains for idle jitter.
- Add docs/decomposition/tooling/internal_authoring.md and CT.M1–M3 module stubs
- Register CT modules and cross-cutting section in decomposition README
- Add internal-content-authoring execution plan
- Extend neon_sprawl_vision.plan.md with Tooling epic (CT) in Core Epic Map and modules
Wire IPositionStateStore to Npgsql when ConnectionStrings:NeonSprawl is set,
with versioned DDL under server/db/migrations and a shared PostgresPositionBootstrap
for schema plus dev-player seeding at host startup. In-memory tests override Postgres
registrations when CI sets the connection string globally.
Add GitHub Actions Postgres service for dotnet test, server README updates,
decomposition and implementation-plan docs, code review write-up, and C# brace
conventions in csharp-style rule.
Implement E1.M1 intent → authority: POST /game/players/{id}/move with v1 JSON
MoveCommandRequest (snap + sequence), extend in-memory store and
PositionStateApi. Godot client splits ground_pick and position_authority_client
with thin main.gd; snap_to_server on player.
Tests live under NeonSprawl.Server.Tests/Game/PositionState/ with AAA layout
and MethodName_ShouldExpectedOutcome_WhenScenario naming. Debug builds use
UseAppHost=false so Linux IDEs resolve .NET 10 from a user-local SDK.
Add NS-16 implementation plan, code review doc, godot-client-script-organization
rule, csharp test layout/naming rules, and refresh E1.M1 / alignment docs.
Server and client README updates include move API, prerequisites, and Rider
notes.
Made-with: Cursor
Add documentation_and_implementation_alignment.md: register Status
semantics, Planned vs partial work, per-module snapshot guidance,
implementation tracking table (E1.M1 + NS-15).
Expand register Status legend; set E1.M1 to In Progress; E1.M1 module
doc Implementation snapshot and status. Link from contracts,
decomposition README, tech_stack.
Made-with: Cursor
Add modules/client_server_authority.md: default server truth for
persistence/economy/combat; intents vs state table; E1.M1–E1.M4 and
E5.M1 ownership; economy/quest/zone/PvP blanket; deferred tick rate
and lag comp.
Link from contracts.md, register, decomposition README, tech_stack;
one-line Authority pointers in E1_M1, E1_M2, E5_M1. Fix contracts.md
baseline link formatting.
Made-with: Cursor
Add modules/contracts.md: Protobuf wire vs server-internal vs JSON Schema
content vs telemetry catalog; mapping Key contracts to kinds; proposed
contracts/ repo layout; versioning and Ready criteria.
Link from dependency register, decomposition README, tech_stack Related
docs; pointer from E9.M1 module doc.
Made-with: Cursor
- Split dependency table by epic; every Depends On ID is a register row
- Add InteractionRequest to E1.M1 contract column per epic 1
- Footnote E4.M3 backend world services; E4.M3 register deps E4.M1 only
- Add 28 new module markdown files (E1.M3–E9.M4 gaps)
- Link per-module docs index to all 37 files
- Cross-link existing module docs; remove obsolete E1.M3 register note from E5.M1
- Update decomposition README modules blurb
Made-with: Cursor
Add nine module documents aligned with module_dependency_register rows
(E1.M1, E1.M2, E2.M2, E3.M2, E4.M4, E5.M1, E6.M1, E7.M1, E9.M1) with
purpose, contracts, dependencies, dependents, slices, and risks.
Link register and decomposition README to the new files; note E1.M3
as E5.M1 dependency without a register row yet.
Made-with: Cursor