← World map and portals · all tasks
Portals you can see and walk through, and a 3D object at every one
world-map-and-portals/004 · created 2026-08-15 · updated 2026-08-18
What landed
Portals were invisible: the client built one hardcoded 10x10 grid for every world and never
rebuilt it, and knew about portal cells only through a --portal-cell= test argument.
hello_ackcarries the Area — dimensions, map name and portal cells. Onhello_ackrather than the director’sworld_statebecause a portal arrival reconnects straight to the destination game port and never asks the director again; anything sent only inworld_statewould leave portal arrivals rendering nothing.- Destinations are never sent. Travel is entirely server-driven, so a client that knew where a portal led could only use it to lie.
WorldGrid.build_for_area()— a real bug found during the build: an authored 40x40 Village kept the 10x10 test grid, soset_portal()’s bounds check silently dropped its portal at (39,20). A 10x10x1 request still returns the deterministic test map, walls and all, so nothing existing changed.WorldGrid.from_area_payload()— the payload-to-grid conversion, static and testable headlessly, which is the only way the client-side work gets a real automated test.- The client rebuilds on arrival —
_build_3d_stage()(environment, sun, camera, entities) is built once;_rebuild_area_geometry()(ground, walls, portals) is freed and recreated under its ownAreaRoot, guarded so aPORTAL_REJECTEDre-hello doesn’t tear the scene down for nothing. portal_node.gd— the placeholder object: an upright gold ring that turns, a sky-blue veil that pulses, motes drifting up. Procedural, in the idiomhumanoid_node.gdestablished (this project has no 3D assets, no Tween, no AnimationPlayer). Meshes are namedportal_ring/portal_veil/portal_motesso a real per-type model is a straight substitution.
The guard that wasn’t needed
The plan called for a re-entry guard against a two-way portal looping on arrival. Tracing it
showed the transfer fires only when a move resolves onto a portal cell, and arriving via
hydrate_entity is not a move — so the loop cannot happen. Rather than add speculative code,
portal-authored.test.ts now pins that behaviour: a traveller lands on the return-leg cell,
sits there without bouncing, then steps off and back on to prove the return leg works.
View source in repository · also available as raw markdown.