Fix the car getting stuck on the Chenaran highway

Traffic cars picked a random direction even on one-way carriageways,
so some drove head-on in the player's lane and stopped dead in front of
them; every bump also threw the player's car backwards while the
traffic car stayed put, so the player could not get past.

- Map data records one-way roads (oneway tags, motorways, roundabouts)
- Traffic drives with the flow in one of two lanes on one-way roads and
  on the right-hand half of two-way roads, spawning only from nearby roads
- Bumps slow the player down instead of reversing them; a traffic car
  pressed against for more than 0.8 s pulls away out of sight
- Regression test for traffic direction and lanes; ?debug collision log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 09:29:58 +03:30
co-authored by Claude Opus 5.5
parent 0939dac9cd
commit 2f74788caa
5 changed files with 92 additions and 16 deletions
+12 -3
View File
@@ -65,7 +65,7 @@ const nameId = name => {
}
return nameIndex.get(name);
};
const roads = [], buildings = [], areas = [], labels = [];
const roads = [], buildings = [], areas = [], labels = [], onewayRoads = [];
const areaKinds = ['park', 'grass', 'forest', 'pitch', 'water', 'campus', 'industrial', 'farmland'];
const areaKind = t => t.leisure === 'park' || t.leisure === 'garden' || t.landuse === 'park' || t.landuse === 'recreation_ground' ? 'park'
: t.landuse === 'grass' ? 'grass'
@@ -104,13 +104,20 @@ for (const e of city) {
// Motorways are drawn and routed like trunk roads.
const highway = t.highway?.replace('_link', '').replace('motorway', 'trunk');
if (e.type === 'way' && highway && roadTypes.includes(highway) && e.geometry?.length > 1) {
// One-way roads are stored pointing in their direction of travel; dual carriageways
// (each side its own way) are one-way, as are motorways and roundabouts by default.
const oneway = ['yes', 'true', '1', '-1'].includes(t.oneway) || (t.oneway !== 'no' && (t.highway.startsWith('motorway') || ['roundabout', 'circular'].includes(t.junction)));
const geometry = t.oneway === '-1' ? [...e.geometry].reverse() : e.geometry;
// Whole ways come back from Overpass, so cut them where they leave the region.
const inRegion = ({lat, lon}) => lat >= REGION.minLat && lat <= REGION.maxLat && lon >= REGION.minLon && lon <= REGION.maxLon;
let run = [];
for (const point of [...e.geometry, null]) {
for (const point of [...geometry, null]) {
if (point && inRegion(point)) run.push(point);
else {
if (run.length > 1) roads.push([roadTypes.indexOf(highway), nameId(t.name), ...flat(run)]);
if (run.length > 1) {
if (oneway) onewayRoads.push(roads.length);
roads.push([roadTypes.indexOf(highway), nameId(t.name), ...flat(run)]);
}
run = [];
}
}
@@ -162,6 +169,8 @@ const city_ = {
attribution: '© OpenStreetMap contributors',
bounds: {minX: min.x, minZ: min.z, maxX: max.x, maxZ: max.z},
roadTypes, areaKinds, names, roads, buildings, areas,
// Indices into `roads` of one-way roads, whose points run in the direction of travel.
oneway: onewayRoads,
labels: [...new Map(labels.map(l => [l.name, l])).values()]
};
writeFileSync(new URL('../src/data/mellat.json', import.meta.url), JSON.stringify(city_));