Skip to content

Steps 25–28: Procedural city, navmesh, homes, transport and EMS continuity - #3

Open
ausdisau wants to merge 13 commits into
feat/step-7-persistent-open-worldfrom
feat/steps-25-28-procedural-city
Open

Steps 25–28: Procedural city, navmesh, homes, transport and EMS continuity#3
ausdisau wants to merge 13 commits into
feat/step-7-persistent-open-worldfrom
feat/steps-25-28-procedural-city

Conversation

@ausdisau

@ausdisau ausdisau commented Aug 24, 2026

Copy link
Copy Markdown
Owner

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 ambulance stage, 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.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d4075f17-cd24-4fa0-b09c-36ed404f8286

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/world/homeTransport.ts Outdated
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];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +67 to +69
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread src/world/homeTransport.ts Outdated
Comment on lines +90 to +91
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 5 potential issues.

Fix All in Cursor

❌ 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;}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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};}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

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;}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a549bbb. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant