Skip to content

SC2 selection and control

Ownership and input

Right-click smartpoint movement checks the selected unit's authored move,Move ability through SC2_HUD_CommandEnabled, just like a command-card click. Disabled, hidden, paused, and foreign units cannot acquire a new player Move order through that path. sc2_control.smartpoint_rejects_disabled_and_paused_move drives the production client-command dispatcher and checks that rejected requests emit no confirmation event or persistent order.

The single local client retains ps.number == 1, matching the current Galaxy PlayerGroupPlayer session contract. Do not infer its identity from placed army size: TRaynor01's script assigns gv_p1_USER = 1, while player 2 owns the largest placed army. UnitCargoCreate asks the game for the transport's owner and creates cargo for that owner. UnitGetOwner uses the same game callback.

Shared client/cl_input.c handles click/rectangle selection and right-click orders exactly as WC3. SC2_CustomizeEntity marks scenery and nonselectable units unselectable. select replaces membership (including empty requests), validates unit visibility and selectability, and returns svc_set_selection. The existing snapshot path publishes RF_SELECTED; the game updates the selected-unit HUD through the existing layout protocol. smartpoint moves the selected owned mobile units, and smart uses a target entity's position. Selection must never suppress the next point order: the shared input already separates the two.

Shared WC3 routing

server/sv_routing.c is the original WC3 router, included by both games' g_world.c inside their server world. It is excluded from the executable's unity source list so it cannot create a competing client-side routing state. server/routing.h contains ROUTEPATH and the default per-tick work budget.

CM_AccelerateRoute is extracted from WC3's persistent accelerated-turn helper; WC3 and SC2 both use it. CM_SlideRoute likewise contains WC3's existing 15-degree left/right heading search, called by both games when the next step is blocked. Without this local steering, SC2 stalled permanently at the radius-expanded wall corner even though the shared field supplied a valid route. The WC3 movement member retains the original field order and types within movement.path. Direct paths, bounded A*, collision-radius checks, incremental flow fields, and unreachable-goal adjustment all use the existing WC3 CM_* implementation. No second pathfinding algorithm is introduced. SC2 services pending fields each tick and does not invalidate all fields per order or substitute straight-line motion when a route is pending. Flying units retain the existing direct flight behavior. Walk/Stand frames use server model sequence intervals instead of assigning absolute server time as an animation frame.

SC2's pre-existing terrain adapter still initializes an open terrain grid and bakes static entity obstacles. Decoding SC2 navigation/pathing layers, richer formation assignment, full WC3-style local unit avoidance, full combat parity, alliance mechanics, and shift order queues remain separate work.

Model feedback

M3 unit forward is -Y. The shared renderer applies SC2's mandatory model basis so angle remains a gameplay heading from +X. Placed objects decode their native mesh rotation through SC2_PlacementHeading, preserving bridges and scenery; Galaxy UnitCreate and UnitSetFacing both decode degrees to radians. See coordinates and model transforms.

R_TraceModel inverse-transforms the cursor segment into M3 model space and tests the authored bounds, returning world-space hit distance. These same bounds serve visibility culling. SC2 loads the shared procedural ring and samples terrain Z at its vertices; the old empty asset hook and Z=0 splats could not show selection on the elevated campaign terrain. The shared renderer draws both selected and hover rings from the existing flags.

Snapshot precision (September 2026)

The cell-stepping report was also a wire-format defect, independent of pathfinding. common/msg.c serialized non-WoW entity origins as three integer shorts and selection radii as an integer short. Runtime logs showed the server publishing radius 0.375 while the renderer received 0, producing a zero-area ring despite a valid texture. Moving positions such as (56.272789, 49.727211) likewise lost their fractional components. Galaxy flight and player orders share this snapshot path, so the dropship cutscene suffered the same stepping.

All games now serialize entity origins with NFT_VECTOR3_FLOAT and radius with NFT_FLOAT, matching the existing exact collision-radius contract. The common client still interpolates previous/current snapshots in CL_AddEntity; no SC2-only interpolation path or alternate coordinate representation is needed. entityState_t is unchanged. Protocol version 5 records the wire change: origins cost six additional bytes when sent for WC3/SC2, and radii two additional bytes when sent. Unchanged fields still consume no delta payload. Rebuild both ends.

Do not fix this by enlarging the circle, snapping the simulation, or giving SC2 another router. Inspect server values and decoded client values independently.

Verification

  • make test-sc2-engine: selection replacement/clearing, owner filtering, immediate point orders, arrival, rotated/scaled M3 picking, a collision-sized wall detour through the shared accelerator/field/slide, fractional Galaxy flight snapshots, cardinal/detour model facing, authored placement, and camera preservation.
  • make test-galaxy: cargo inherits a non-default transport owner; invalid cargo creation does not spawn units.
  • make test: includes fractional/large-radius snapshot round trips, WC3 movement/pathfinding and save/load coverage after the shared router move.

For a paced campaign run (frame limits count main-loop iterations, not server ticks):

build/bin/opensc2 -data data/StarCraft2 +com_maxfps 60 +vid_hidden 1 \
  +map Maps/Campaign/TRaynor01.SC2Map +screenshot 1800 +com_frame_limit 2100

Startup logs identify Galaxy-created entity numbers. After the drop, cmd select <ids...> and cmd smartpoint <x> <y> exercise the same authoritative commands as mouse input. select 0 clears the rings. Use the shared group recall twice to center the camera on selected units.

Command card and diagnostics

The selected unit's authored card now routes Move, Stop, Hold Position, Patrol and Attack to game/skills/s_orders.c. Point commands retain a pending target until a valid click or Escape. Stop interrupts movement; Hold stays in place; Patrol stores both endpoints. Attack uses the authored weapon range, period and direct effect damage, plus unit armor. Projectile travel, bonuses, effect chains and the complete SC2 ability catalog remain unimplemented.

cmd unitinfo reports selected entity, owner, vital state, position, current order, movement and pending targeting to stderr. Use this alongside screenshots to verify that visible command controls affect simulation. cmd select <entity> is useful for isolating HUD presentation from renderer picking.

TRaynor01's defeat trigger needs real UnitGroup and UnitFilter queries. Returning an empty stub group falsely declared defeat despite live army units. The native query now matches authored attributes/planes/flags, ownership, regions and supported runtime state bits. Unsupported filter semantics produce an explicit VM error.