Steps 25–28: Procedural city, navmesh, homes, transport and EMS continuity - #3
Steps 25–28: Procedural city, navmesh, homes, transport and EMS continuity#3ausdisau wants to merge 13 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1de392b680
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const target=this.store.entity(targetId)?.position??buildingPosition(this.plan,targetId);if(!target)continue; | ||
| const [x,y,z]=vehicle.position,dx=target[0]-x,dz=target[2]-z,d=Math.hypot(dx,dz); | ||
| if(d<1){vehicle.position=target;vehicle.currentStopIndex=vehicle.nextStopIndex;vehicle.status='dwelling';vehicle.dwellSeconds=12;this.completeTripsAtStop(vehicle,targetId);continue;} | ||
| const amount=Math.min(d,vehicle.speedMetresPerSecond*seconds);vehicle.position=[x+dx/d*amount,y,z+dz/d*amount]; |
There was a problem hiding this comment.
Move boarded passengers with the shuttle
When a trip is boarded, this loop updates only the vehicle position; the passenger's world entity remains behind and continues to be moved independently by WorldRuntime.advanceActivity. Consequently, a trip can later be marked completed at its destination while the passenger is still elsewhere in the city, so rendered travel and downstream proximity interactions are incorrect.
Useful? React with 👍 / 👎.
| if(unit.status==='transporting'&&personId){ | ||
| unit.target=hospital; | ||
| if(this.move(unit,seconds)){unit.status='at-hospital';this.continuity.advance(personId,'ed','autonomous emergency transport arrived at ED');this.store.recordEvent('ems.hospital-arrival',personId,`${unit.id}; handover to ED continuity stage`);}continue; |
There was a problem hiding this comment.
Keep the patient attached to the transporting ambulance
During an active EMS transport, only the unit is moved; the assigned person's position and autonomous activity remain unchanged. When the ambulance reaches the hospital, the continuity episode is nevertheless advanced to ed, leaving the patient represented at the scene or roaming the city while the state records an ED handover.
Useful? React with 👍 / 👎.
| const trip:TransportTripRequest={id,personId,originId,destinationId,status:'allocated',vehicleId:vehicle.id,reason:'accessible capacity allocated; boarding remains an explicit action',supportRequested:false}; | ||
| this.trips.set(id,trip);if(usesWheelchair)vehicle.reservedWheelchairSpaces.push(personId); |
There was a problem hiding this comment.
Prevent duplicate trip requests from consuming wheelchair spaces
Each allocated wheelchair trip immediately appends the person ID without checking for an existing allocation or providing any cancellation/expiry path. Repeatedly pressing the new request button three times for Maya therefore fills all three wheelchair spaces with duplicate reservations, after which further requests are reported unavailable even though nobody has boarded.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 5 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.
| unit.target=hospital; | ||
| if(this.move(unit,seconds)){unit.status='at-hospital';this.continuity.advance(personId,'ed','autonomous emergency transport arrived at ED');this.store.recordEvent('ems.hospital-arrival',personId,`${unit.id}; handover to ED continuity stage`);}continue; | ||
| } | ||
| if(unit.status==='at-hospital'){unit.sceneElapsedSeconds+=seconds;if(unit.sceneElapsedSeconds>=35){unit.status='clearing';unit.target=base;unit.sceneElapsedSeconds=0;}continue;} |
There was a problem hiding this comment.
EMS hospital dwell timer wrong
Medium Severity
sceneElapsedSeconds is reset when entering on-scene, but not when entering at-hospital. The hospital wait therefore continues from the leftover on-scene value (~20s), so units clear after roughly 15s instead of the intended 35s educational dwell.
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.
| requestSupport(tripId:string){const t=this.trips.get(tripId);if(!t)return false;t.supportRequested=true;this.store.recordEvent('transport.support.requested',t.personId,`trip=${tripId}; explicit request`);return true;} | ||
| board(tripId:string){const t=this.trips.get(tripId);if(!t||t.status!=='allocated'||!t.vehicleId)return false;const v=this.vehicles.get(t.vehicleId);if(!v||v.status!=='dwelling')return false;const currentStop=v.route[v.currentStopIndex];if(currentStop!==t.originId)return false;const p=this.store.entity(t.personId);const stop=this.stopPosition(currentStop);if(!p||!stop||Math.hypot(p.position[0]-stop[0],p.position[2]-stop[2])>6){this.store.recordEvent('transport.boarding.wait',t.personId,`trip=${tripId}; person is not at boarding stop; intent preserved`);return false;}if(!v.passengers.includes(t.personId))v.passengers.push(t.personId);t.status='boarded';this.store.recordEvent('transport.boarded',t.personId,`vehicle=${v.id}; destination=${t.destinationId}; explicit boarding action`);return true;} | ||
| step(seconds:number){for(const v of this.vehicles.values()){if(v.status==='out-of-service')continue;if(v.status==='dwelling'){v.dwellSeconds-=seconds;if(v.dwellSeconds<=0){v.status='moving';v.nextStopIndex=(v.currentStopIndex+1)%v.route.length;}continue;}const targetId=v.route[v.nextStopIndex];if(!targetId)continue;const target=this.stopPosition(targetId);if(!target)continue;const[x,y,z]=v.position,dx=target[0]-x,dz=target[2]-z,d=Math.hypot(dx,dz);if(d<1){v.position=target;v.currentStopIndex=v.nextStopIndex;v.status='dwelling';v.dwellSeconds=12;this.completeTripsAtStop(v,targetId);continue;}const amount=Math.min(d,v.speedMetresPerSecond*seconds);v.position=[x+dx/d*amount,y,z+dz/d*amount];}} | ||
| private stopPosition(id:string):Vec3|undefined{return this.store.entity(id)?.position??buildingPosition(this.plan,id);}private completeTripsAtStop(v:TransportVehicleState,stopId:string){for(const t of this.trips.values())if(t.vehicleId===v.id&&t.status==='boarded'&&t.destinationId===stopId){t.status='completed';v.passengers=v.passengers.filter(id=>id!==t.personId);v.reservedWheelchairSpaces=v.reservedWheelchairSpaces.filter(id=>id!==t.personId);this.store.recordEvent('transport.trip.completed',t.personId,`arrived=${stopId}`);}}private homeFromBuilding(b:CityBuilding):HomeState{return{id:b.id,residentIds:[...b.residentIds],entryOperational:true,mainsPower:true,backupPower:b.access.charging,chargingAvailable:b.access.charging,quietSpace:b.access.quietSpace};}} |
There was a problem hiding this comment.
Trips complete without relocating riders
High Severity
completeTripsAtStop marks boarded trips completed and records arrival, but never moves the person to the stop. Riders also stay at the origin while the vehicle travels, so world position never reflects the journey the service claims finished.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.
| } | ||
| if(unit.status==='transporting'&&personId){ | ||
| unit.target=hospital; | ||
| if(this.move(unit,seconds)){unit.status='at-hospital';this.continuity.advance(personId,'ed','autonomous emergency transport arrived at ED');this.store.recordEvent('ems.hospital-arrival',personId,`${unit.id}; handover to ED continuity stage`);}continue; |
There was a problem hiding this comment.
EMS omits patient relocation
High Severity
During transporting, only the unit moves. On hospital arrival the engine advances continuity to ed and logs handover, but the person's world position stays in the community, so spatial state contradicts the continuity stage.
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.
| setHomePower(homeId:string,mainsPower:boolean){const h=this.homes.get(homeId);if(!h)return false;h.mainsPower=mainsPower;this.store.recordEvent('home.power.changed',homeId,`mains=${mainsPower}; backup=${h.backupPower}; charging=${h.chargingAvailable}`);return true;} | ||
| requestTrip(personId:string,originId:string,destinationId:string):TransportTripRequest|undefined{const e=this.store.entity(personId);if(!e||e.kind!=='person')return undefined;const p=e as PersonEntity,v=[...this.vehicles.values()].find(i=>i.status!=='out-of-service'),id=`trip-${++this.requestCounter}`;if(!v){const t:TransportTripRequest={id,personId,originId,destinationId,status:'unavailable',reason:'no represented transport vehicle available; preserve travel goal',supportRequested:false};this.trips.set(id,t);return clone(t);}const wc=p.mobility.mode==='powered-wheelchair'||p.mobility.mode==='manual-wheelchair',wcAvailable=v.reservedWheelchairSpaces.length<v.wheelchairSpaces,capacity=v.passengers.length<v.capacity;if(!capacity||(wc&&!wcAvailable)){const t:TransportTripRequest={id,personId,originId,destinationId,status:'unavailable',reason:wc?'wheelchair space unavailable on current service':'vehicle capacity unavailable',supportRequested:false};this.trips.set(id,t);return clone(t);}const t:TransportTripRequest={id,personId,originId,destinationId,status:'allocated',vehicleId:v.id,reason:'accessible capacity allocated; boarding remains an explicit in-place action',supportRequested:false};this.trips.set(id,t);if(wc)v.reservedWheelchairSpaces.push(personId);this.store.recordEvent('transport.trip.allocated',personId,`${originId}->${destinationId}; vehicle=${v.id}; support not assumed`);return clone(t);} | ||
| requestSupport(tripId:string){const t=this.trips.get(tripId);if(!t)return false;t.supportRequested=true;this.store.recordEvent('transport.support.requested',t.personId,`trip=${tripId}; explicit request`);return true;} | ||
| board(tripId:string){const t=this.trips.get(tripId);if(!t||t.status!=='allocated'||!t.vehicleId)return false;const v=this.vehicles.get(t.vehicleId);if(!v||v.status!=='dwelling')return false;const currentStop=v.route[v.currentStopIndex];if(currentStop!==t.originId)return false;const p=this.store.entity(t.personId);const stop=this.stopPosition(currentStop);if(!p||!stop||Math.hypot(p.position[0]-stop[0],p.position[2]-stop[2])>6){this.store.recordEvent('transport.boarding.wait',t.personId,`trip=${tripId}; person is not at boarding stop; intent preserved`);return false;}if(!v.passengers.includes(t.personId))v.passengers.push(t.personId);t.status='boarded';this.store.recordEvent('transport.boarded',t.personId,`vehicle=${v.id}; destination=${t.destinationId}; explicit boarding action`);return true;} |
There was a problem hiding this comment.
Transport capacity over-allocates seats
Medium Severity
requestTrip gates capacity on passengers.length only, reserves wheelchair spaces without reserving a seat, and board never rechecks capacity. Allocated-but-unboarded wheelchair holds also never clear, so spaces can leak permanently.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.
|
|
||
| dispatchFromContinuity(personId:string,reason:string){ | ||
| const episode=this.continuity.episode(personId);if(!episode||episode.stage!=='ambulance')return false; | ||
| if([...this.units.values()].some(unit=>unit.assignedPersonId===personId&&unit.status!=='available'))return true; |
There was a problem hiding this comment.
Stale EMS assignment blocks redispatch
Medium Severity
dispatchFromContinuity returns success if any unit still has assignedPersonId with a non-available status. During at-hospital/clearing that assignment remains, so a new ambulance-stage episode reports dispatch success without sending a unit.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit a549bbb. Configure here.


Stacked on Steps 7–24. Adds deterministic procedural city generation, a triangulated accessibility-aware navmesh with A* routing, persistent home and accessible transport service state, and autonomous emergency-service response tied to the existing evidence-gated continuity engine. Babylon/WebXR renders city buildings, roads, a navmesh overlay, accessible transport and ambulance units. Person authority and communication state remain preserved; transport support and boarding are explicit rather than assumed. Simulation gradients, speeds and timings are educational model parameters, not statutory accessibility or clinical treatment thresholds.
Note
Medium Risk
Touches core world-tick, navigation, continuity, and EMS simulation, so routing or dispatch bugs can change educational scenarios. Not auth/security-critical; parameters are simulation-only.
Overview
Expands the open world from a campus scene into a deterministic procedural city (steps 25–28): districts, roads, homes and civic buildings are generated, applied to persistent world entities, and rendered in Babylon/WebXR with a toggleable navmesh overlay.
Navigation now uses a triangulated city navmesh and A* with person-specific gradient/access costs. Travel prefers navmesh waypoints while still using the legacy graph for doors/lifts.
Homes and city transport track power/charging and allocate wheelchair-aware capacity. Allocation is not boarding: boarding stays an explicit, in-place action and support is never assumed.
EMS dispatches only when a continuity episode is in the
ambulancestage, then drives units through on-scene → hospital handover (advancing continuity to ED) without inferring treatment. Person authority is preserved throughout. Tests cover determinism, pathfinding, trip allocation, and dispatch gating.Reviewed by Cursor Bugbot for commit a549bbb. Bugbot is set up for automated code reviews on this repo. Configure here.