23 Commits

Author SHA1 Message Date
Joshua Deville
5c6b038188 Complete Phase 2b: block placement/removal, ship-relative snapping, mouse-look, visual polish
Place/remove (task #21): all four block types (Hull/Thruster/Power/Storage)
now have working place chains on BP_PlayerCharacter (keys 1-4), plus a
remove key (X) that raycasts to a targeted block, verifies it via
Array_Contains against PlacedBlocks before destroying it (guards against
deleting terrain/ship/other actors), and calls RemoveBlockFromArray.

Stat aggregation (task #22): rewrote RecalculateStats on BP_ShipPawn to
iterate PlacedBlocks with a ForEachLoop, casting each element to its block
class and summing bonuses onto base values, instead of reading four fixed
Slot_* variables. Multiple blocks of the same type now correctly stack
(e.g. two Hull blocks = +100, not capped at one). Also fixed a real bug
found during manual playtesting: AddBlockToArray/RemoveBlockFromArray never
actually called RecalculateStats (dead-end exec pins), so stats never
updated after the initial BeginPlay call regardless of what was placed.

Ship-relative placement: raycast hit location is now converted into the
ship's local space, clamped to a 600-unit radius, snapped to the 200-unit
grid, then converted back to world space. Recomputed every tick from the
ship's current transform, so placement always stays near/aligned with the
ship instead of landing at arbitrary world coordinates. Also fixed a
related bug: the raycast never checked bBlockingHit, so a miss (e.g. aiming
at open sky) silently fell back to world origin (0,0,0) -- coincidentally
the ship's spawn point -- making blocks appear to "teleport" there and then
trail behind once the ship moved. Added bHasValidBuildTarget, gating all
four placement branches on an actual hit.

Mouse-look: extended the UnrealMCPython plugin itself (hot-patched via
Live Coding) with a new "InputAxisKey" node type wrapping
UK2Node_InputAxisKeyEvent::Initialize(FKey), since the existing "InputKey"
node type only supports press/release button semantics, not continuous
axis values. Wired Mouse X/Y to AddControllerYawInput/AddControllerPitchInput
(Y inverted), switched bUseControllerRotationYaw/Pitch back on, and
repurposed the old A/D actor-rotation logic into A/D strafe via
AddMovementInput(RightVector). Also added mouse-wheel zoom on the ship's
CameraBoom (TargetArmLength, clamped 300-2000).

Visual polish: ship mesh was a single scaled Cone; added a cylinder
fuselage and two wing panels for a recognizable ship silhouette. Added
distinct colored materials per block type (Hull/Thruster/Power/Storage)
plus a translucent ghost-preview material, replacing the default gray
checker look. Added a SkyAtmosphere actor to fix the "will render black"
sky light warning that made the level unreadable.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 22:32:38 -04:00
Joshua Deville
7dc47acdb0 Add AddBlockToArray/RemoveBlockFromArray events on BP_ShipPawn
PlacedBlocks array + PendingBlockRef variable + two CustomEvents that
append/remove PendingBlockRef via the now-fixed Array_Add/
Array_RemoveItem. Verified compiling with fully-resolved (non-
wildcard) pin types. Not yet wired to any place/remove key input --
that's the remaining part of task #21.
2026-07-08 22:49:44 -04:00
Joshua Deville
536f828b6c Fix wildcard array library functions (Array_Add, Array_RemoveItem, etc)
Real root cause, found after two incorrect attempts: ConnectBlueprintPins
always instantiated the generic UK2Node_CallFunction for every CallFunction
node, including wildcard array library functions like Array_Add and
Array_RemoveItem. Only UK2Node_CallArrayFunction (a UK2Node_CallFunction
subclass) overrides NotifyPinConnectionListChanged to resolve its wildcard
TargetArray/Item pins from the connected array's type -- the generic base
class has a no-op default, so no connection-notification of any kind could
ever have fixed it. This also explains why the ForEachLoop fix (a
MacroInstance, unrelated class hierarchy) worked while this didn't, despite
looking like the same "wildcard pin" symptom.

Debugging trail for the record: first tried NodeConnectionListChanged()
(wrong function, no args -- happened to be irrelevant to both bugs and
coincidentally look like it fixed ForEachLoop). Then tried adding
ReconstructNode() (no effect). Then matched the schema's actual
TryCreateConnection code exactly (PinConnectionListChanged(Pin) with the
specific pin) -- correct in general (this IS what fixes MacroInstance
wildcards, confirmed via UE_LOG marker showing the call executes), but
insufficient here because the node class itself was wrong.

Fix: check TargetFunc->HasMetaData(TEXT("ArrayParm")) (the same meta key
UK2Node_CallArrayFunction itself checks) and instantiate the correct class
before calling SetFromFunction(). Verified: Array_Add's TargetArray/NewItem
pins now show concrete "object/Actor" type instead of wildcard, and the
containing Blueprint compiles.
2026-07-08 22:49:16 -04:00
Joshua Deville
226a979d66 Fix hard crash in SpawnActor node creation
UK2Node_SpawnActorFromClass::PostPlacedNewNode() (engine source)
overrides the base class without calling Super::PostPlacedNewNode(),
and unconditionally dereferences GetScaleMethodPin() via
FindPinChecked -- but that pin is only created in
AllocateDefaultPins(), which FGraphNodeCreator::Finalize() always
calls *after* PostPlacedNewNode(). Result: a hard assertion failure
(EdGraphNode.h:586) that crashes the whole editor, every time,
regardless of any node_json parameters.

The real editor UI never hits this because it creates nodes through
a different path (node spawner templates) that doesn't have this
ordering problem. Our plugin uses the generic FGraphNodeCreator
pattern directly, so it does.

Fix: manually call SpawnNode->AllocateDefaultPins() before
Creator.Finalize(), so pins already exist by the time
PostPlacedNewNode() runs. Verified: SpawnActor node now creates
cleanly with all expected pins (Class, SpawnTransform, etc), editor
stayed stable and MCP connection alive afterward.

Rebuilt via Live Coding successfully this time (Ctrl+Alt+F11) --
the earlier UbaCli spawn failure appears to have been a one-off,
possibly from a conflicting concurrent Build.bat invocation.
2026-07-08 22:33:50 -04:00
Joshua Deville
58794dbd25 Add ghost preview block for build mode
BuildGhost: a pre-placed cube actor (not runtime-spawned, to avoid
the SpawnActor Class-pin problem for this piece) referenced via
GhostBlockRef. Shown and moved to SnappedLocation each tick while
bBuildMode is on; hidden otherwise. Collision left enabled on the
ghost (couldn't find the right property path to disable it through
this tool -- BodyInstance.CollisionEnabled isn't a flat property);
minor known edge case where the raycast could self-hit the ghost,
not addressed yet.
2026-07-08 21:04:50 -04:00
Joshua Deville
f7d0b17be8 Add build-mode toggle and grid-snap raycast on BP_PlayerCharacter
B key toggles bBuildMode. While active, Tick line-traces from the
character's eye height along its forward vector, breaks the hit
result, and snaps the impact point to a 200-unit grid via
Vector_SnappedToGrid, storing the result in SnappedLocation. No
visual feedback or placement yet (next: ghost preview, then
place/remove).

Uses BreakHitResult (a multi-output-pin CallFunction on
GameplayStatics) to extract struct fields, since a dedicated
"BreakStruct" node type isn't supported by this tool -- works fine
as a plain function call.
2026-07-08 21:02:19 -04:00
Joshua Deville
8ac602168d Fix wildcard pin resolution in UnrealMCPython's ConnectBlueprintPins
ConnectBlueprintPins called SourcePin->MakeLinkTo(TargetPin) but never
notified either node of the new connection, so wildcard-typed pins
(MacroInstance nodes like ForEachLoop, and by extension any wildcard
array/map library call) never resolved their type -- compilation
failed with "type ... is undetermined" regardless of how many times
you recompiled. The real graph editor triggers this via the schema's
TryCreateConnection; MakeLinkTo alone doesn't.

Fix: call NodeConnectionListChanged() on both nodes after linking,
matching what the schema does. Verified against the exact ForEachLoop
scenario that failed twice before the fix -- the Array pin now
resolves to a concrete type and the graph compiles.

Rebuilt via full offline Build.bat (UnrealEditor target) rather than
Live Coding -- Live Coding's patch-link step hit a persistent
"Could not spawn process UbaCli.exe (Error 267)" in this environment,
unrelated to the code change itself (the .cpp compiled cleanly both
times; only the hot-patch link step failed).
2026-07-08 20:37:33 -04:00
Joshua Deville
fb74e789af Verify block stat aggregation with one of each block attached
One Hull/Thruster/Power/Storage block placed near the ship spawn and
wired into the corresponding Slot_* references. Verified via Python
(direct RecalculateStats call + property reads): MaxHull 150,
MaxPower 150, StorageCapacity 30, ShipMovement.MaxSpeed 4500 -- all
exactly base + block bonus. Confirms the block-slot aggregation
system works end-to-end. Smoke-tested in PIE: no runtime errors.
2026-07-08 16:28:07 -04:00
Joshua Deville
a4da93cc63 Add block-slot stat aggregation to BP_ShipPawn (Phase 2a)
Four typed slot variables (Slot_Hull/Thruster/Power/Storage, one per
block class) plus a RecalculateStats event that reads each slot (via
IsValid + external-member Get on the slot's block instance) and
derives MaxHull, ShipMovement.MaxSpeed, MaxPower/Power, and
StorageCapacity from base values plus whatever's attached. Called at
the end of BeginPlay. All slots empty = base values, matching current
gameplay exactly.

New technique discovered: VariableGet/VariableSet support an external
member reference via a "variable_class" field (full asset path
required for Blueprint-generated classes, not just the short class
name) -- this is how block-instance properties and component
properties (e.g. FloatingPawnMovement.MaxSpeed) get read/written at
runtime without needing a formal Function or the broken array/loop
path.
2026-07-08 16:25:06 -04:00
Joshua Deville
2f161b0105 Add ship block Blueprint classes (Phase 2a)
BP_Block_Hull, BP_Block_Thruster, BP_Block_Power, BP_Block_Storage:
simple Actors with a mesh and a single contribution variable each
(HullBonus/ThrustBonus/PowerBonus/StorageBonus). Distinct shapes/
scales for visual identification.

Array variables and the ForEachLoop macro don't reliably work
through this MCP plugin's Blueprint graph builder (confirmed via a
throwaway test: the macro's wildcard Array pin never resolves its
type, compile fails every time) -- so aggregation will use one typed
slot per block type on the ship rather than a generic block list.
2026-07-08 16:17:43 -04:00
Joshua Deville
f2f759e912 Add basic resource gathering into ship storage
ResourceNode_01 placed in the landing zone. E key on the character:
checks distance to the resource node (via a typed ResourceNodeRef
variable, same object-reference technique as the ship/character
cross-refs), and if within 300 units calls AddStorage on the ship
(through ShipPawnRef) and destroys the node. AddStorage increments
StorageAmount up to StorageCapacity, printing feedback either way.
Smoke-tested in PIE: no runtime errors.
2026-07-08 16:03:12 -04:00
Joshua Deville
090b19440d Fix A/D turning on BP_PlayerCharacter
ACharacter defaults bUseControllerRotationYaw to true, which makes
the Controller's rotation overwrite the actor's rotation every tick
-- stomping our manual AddActorLocalRotation calls almost immediately
(visible as a barely-perceptible tick instead of a turn). Plain Pawn
(the ship) doesn't have this default, which is why the same rotation
logic worked fine there. Disabled it on both the class default and
the placed instance.
2026-07-08 15:57:13 -04:00
Joshua Deville
0744be80bd Wire up ship exit/enter possession transition
F key on the ship: teleports the player character to just above the
ship's location and possesses it. F key on the character: possesses
the ship back. Same controller, so possessing one pawn automatically
unpossesses the other.

Notable implementation detail: GameplayStatics::GetActorOfClass
looked like the obvious way to find "the other pawn" at runtime, but
this MCP plugin's pin_defaults mechanism can't correctly set a Class-
type pin (it accepts the string silently but the compiler later
rejects it as an invalid default) -- there's no supported node type
for a class-literal either. Worked around it by adding real object-
reference variables (PlayerCharacterRef / ShipPawnRef) via
unreal.BlueprintEditorLibrary.get_object_reference_type() directly
through the Python escape hatch, marking them instance-editable, and
wiring the actual actor references between the two placed level
instances. Avoids needing any runtime class lookup at all.
2026-07-08 15:48:29 -04:00
Joshua Deville
d6f7271bed Add on-foot player character with suit oxygen
BP_PlayerCharacter (Character-based): first-person camera, WASD
movement + A/D turning (same input scheme as the ship), SuitOxygen
stat that depletes each tick, clamped at 0. Not auto-possessed yet --
that's the ship exit/enter transition (next task). Placed one
instance in L_TestFlight near the ship spawn.

Also reverted level gravity to default (-980): FloatingPawnMovement
(ship) never reads world gravity regardless of its value, but
CharacterMovementComponent (this character) needs real gravity to
walk normally. Zero gravity was an unnecessary leftover from Phase 0
that would have broken on-foot movement.
2026-07-08 15:38:15 -04:00
Joshua Deville
2dd36c6537 Add landable zone to L_TestFlight
Added a landing zone as a region of the existing test level (at
X=8000) rather than a separate level/L_Landing01 -- avoids needing
level-streaming and cross-level pawn-transfer machinery that Phase 1
doesn't need yet. Ground platform (scaled plane), 3 scattered rock
obstacles, and a flat landing pad marker disc.
2026-07-08 15:33:55 -04:00
Joshua Deville
4cc950f222 Implement hull damage/repair on BP_ShipPawn
DamageHull and RepairHull custom events (this MCP plugin's Blueprint
graph builder can't author formal parameterized Functions yet, only
CustomEvents, so amounts are fixed rather than passed in):
- DamageHull: Hull -= 10, clamped to 0.
- RepairHull: if StorageAmount > 0, consumes 1 storage and restores
  20 hull (clamped to MaxHull); otherwise no-ops.
Bound to H/R test keys with on-screen PrintString feedback, since
there's no HUD yet to show the actual Hull/Storage values.
No real damage trigger (e.g. collision) wired up yet -- nothing to
collide with in the level yet. Smoke-tested in PIE: no runtime
errors.
2026-07-08 15:30:33 -04:00
Joshua Deville
6af5407788 Fix chase camera parenting; add reference markers to test level
CameraBoom was parented to ShipMesh, which is rotated 90deg to point
the placeholder cone forward -- the camera inherited that rotation,
producing a badly skewed near-edge-on view. Reparented CameraBoom to
the actor root instead, so its pitch is relative to actor-forward,
not the mesh's cosmetic rotation.

Also added 5 marker cubes around the spawn point: the level was
completely empty, so with a rigidly-attached chase camera, forward/
backward translation had zero visual reference and could look like
nothing was happening even if movement was working correctly.
2026-07-08 15:17:48 -04:00
Joshua Deville
e0fd4c28a7 Add basic lighting to L_TestFlight
The level had zero actors besides the ship, so it rendered fully
black (no directional light, no sky light). Added a DirectionalLight
and a real-time-capture SkyLight so the placeholder ship is actually
visible during PIE testing.
2026-07-08 15:13:38 -04:00
Joshua Deville
a5a068d1cf Wire up basic flight controls on BP_ShipPawn
W/S thrust forward/back, A/D yaw left/right, via direct key-binding
(InputKey nodes) rather than Enhanced Input -- this MCP plugin's
Blueprint graph builder doesn't support authoring Enhanced Input
Action nodes yet, only raw key events. Tick applies ThrustAxis/
YawAxis each frame via AddMovementInput/AddActorLocalRotation.
Bumped FloatingPawnMovement Max Speed/Acceleration for a snappier
placeholder feel. Smoke-tested in PIE: no runtime errors.
2026-07-08 15:08:59 -04:00
Joshua Deville
987f4cc0ef Add placeholder ship pawn Blueprint
BP_ShipPawn: cone mesh, FloatingPawnMovement for gravity-free flight,
spring-arm chase camera. Placed in L_TestFlight and set to
auto-possess Player 0 for PIE testing. No input wiring yet.
2026-07-08 15:01:28 -04:00
Joshua Deville
496320cc8c Add empty test level and Unreal MCP tooling
- L_TestFlight: empty, zero-gravity level for Phase 0 flight testing,
  set as editor/game default map.
- Enable UnrealMCPython + Python Editor Script Plugin, third-party
  MCP plugin (GenOrca/unreal-mcp v2.2.0) giving direct editor control
  (actors, blueprints, levels, materials, etc.) alongside Epic's
  built-in Unreal MCP plugin.
- Vendor the mcp-server Python source used to bridge to the plugin.
- .mcp.json intentionally gitignored (machine-specific absolute paths).
2026-07-08 14:56:57 -04:00
Joshua Deville
43494a614b Add UE5.8 Blueprint project skeleton
Blank Blueprint-only project (no C++ module), created via editor
project wizard, per the Blueprint-first decision in 03_TECH_STACK.md.
2026-07-08 14:27:16 -04:00
Joshua Deville
e437a18121 Add project planning docs and repo scaffolding
Vision, requirements, tech stack, and phased roadmap for the space
survival game, plus Unreal-appropriate .gitignore/.gitattributes
and Git LFS setup.
2026-07-08 13:47:02 -04:00