feat(prototype): crossing patrol routes and through-transit objects
Mobile obstacles used to bounce until they wedged into far corners, and a single perimeter route let the mesh stretch into segments that stopped exchanging data. Even drones now fly the perimeter while odd drones fly an X through the center, keeping bridge links alive, and transit objects enter at one edge, cross the area, and respawn at a fresh edge.
This commit is contained in:
@@ -141,7 +141,7 @@ export default function App(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
{ob.moving && (
|
{ob.moving && (
|
||||||
<text x={sx(ob.pos.x)} y={sy(ob.pos.y) + 4} style={styles.obLabel as never}>
|
<text x={sx(ob.pos.x)} y={sy(ob.pos.y) + 4} style={styles.obLabel as never}>
|
||||||
mobile
|
transit
|
||||||
</text>
|
</text>
|
||||||
)}
|
)}
|
||||||
</g>
|
</g>
|
||||||
@@ -186,7 +186,7 @@ export default function App(): JSX.Element {
|
|||||||
<div style={styles.dim}>blue line — pose broadcast (5 Hz, ~46 B)</div>
|
<div style={styles.dim}>blue line — pose broadcast (5 Hz, ~46 B)</div>
|
||||||
<div style={styles.dim}>green line — bulk sync (sealed partitions)</div>
|
<div style={styles.dim}>green line — bulk sync (sealed partitions)</div>
|
||||||
<div style={styles.dim}>flash — broadcast event delivered</div>
|
<div style={styles.dim}>flash — broadcast event delivered</div>
|
||||||
<div style={styles.dim}>red circle — mobile obstacle</div>
|
<div style={styles.dim}>red circle — transit object crossing the area</div>
|
||||||
</Section>
|
</Section>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+55
-16
@@ -65,12 +65,52 @@ function rnd(): number {
|
|||||||
return ((seedState >>> 0) % 100000) / 100000;
|
return ((seedState >>> 0) % 100000) / 100000;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WAYPOINTS: Vec[] = [
|
// Two interleaved patrol routes: even drones circle the perimeter, odd
|
||||||
|
// drones fly an X through the center. Paths cross mid-field, so the mesh
|
||||||
|
// keeps bridging between the front and the back of the formation instead
|
||||||
|
// of stretching into disconnected segments along one loop.
|
||||||
|
const PERIMETER: Vec[] = [
|
||||||
{ x: 120, y: 120 },
|
{ x: 120, y: 120 },
|
||||||
{ x: AREA - 120, y: 150 },
|
{ x: AREA - 120, y: 150 },
|
||||||
{ x: AREA - 150, y: AREA - 120 },
|
{ x: AREA - 150, y: AREA - 120 },
|
||||||
{ x: 150, y: AREA - 150 },
|
{ x: 150, y: AREA - 150 },
|
||||||
];
|
];
|
||||||
|
const CROSS: Vec[] = [
|
||||||
|
{ x: 150, y: 150 },
|
||||||
|
{ x: AREA - 150, y: AREA - 150 },
|
||||||
|
{ x: AREA - 150, y: 150 },
|
||||||
|
{ x: 150, y: AREA - 150 },
|
||||||
|
];
|
||||||
|
function routeOf(d: Drone): Vec[] {
|
||||||
|
return d.id % 2 === 0 ? PERIMETER : CROSS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transit objects enter at one edge, cross the whole area, and respawn at
|
||||||
|
// a fresh edge once they leave — a stream of through-traffic instead of
|
||||||
|
// obstacles bouncing until they wedge into a corner.
|
||||||
|
const TRANSIT_MARGIN = 120;
|
||||||
|
function spawnTransit(id: number): Obstacle {
|
||||||
|
const speed = 26 + rnd() * 30;
|
||||||
|
const along = 120 + rnd() * (AREA - 240);
|
||||||
|
const skew = (rnd() - 0.5) * speed * 0.9; // diagonal-ish crossings
|
||||||
|
const edge = Math.floor(rnd() * 4);
|
||||||
|
const table: { pos: Vec; vel: Vec }[] = [
|
||||||
|
{ pos: { x: along, y: -TRANSIT_MARGIN + 20 }, vel: { x: skew, y: speed } },
|
||||||
|
{ pos: { x: along, y: AREA + TRANSIT_MARGIN - 20 }, vel: { x: skew, y: -speed } },
|
||||||
|
{ pos: { x: -TRANSIT_MARGIN + 20, y: along }, vel: { x: speed, y: skew } },
|
||||||
|
{ pos: { x: AREA + TRANSIT_MARGIN - 20, y: along }, vel: { x: -speed, y: skew } },
|
||||||
|
];
|
||||||
|
const { pos, vel } = table[edge];
|
||||||
|
return { id, pos, radius: 22 + rnd() * 16, moving: true, vel };
|
||||||
|
}
|
||||||
|
function outOfTransit(ob: Obstacle): boolean {
|
||||||
|
return (
|
||||||
|
ob.pos.x < -TRANSIT_MARGIN ||
|
||||||
|
ob.pos.x > AREA + TRANSIT_MARGIN ||
|
||||||
|
ob.pos.y < -TRANSIT_MARGIN ||
|
||||||
|
ob.pos.y > AREA + TRANSIT_MARGIN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function makeWorld(droneCount: number): World {
|
export function makeWorld(droneCount: number): World {
|
||||||
const drones: Drone[] = Array.from({ length: droneCount }, (_, i) => {
|
const drones: Drone[] = Array.from({ length: droneCount }, (_, i) => {
|
||||||
@@ -83,7 +123,7 @@ export function makeWorld(droneCount: number): World {
|
|||||||
},
|
},
|
||||||
vel: { x: 0, y: 0 },
|
vel: { x: 0, y: 0 },
|
||||||
heading: angle,
|
heading: angle,
|
||||||
waypoint: i % WAYPOINTS.length,
|
waypoint: i % PERIMETER.length,
|
||||||
battery: 90 + rnd() * 10,
|
battery: 90 + rnd() * 10,
|
||||||
channel: CHANNELS[i % CHANNELS.length],
|
channel: CHANNELS[i % CHANNELS.length],
|
||||||
};
|
};
|
||||||
@@ -92,8 +132,9 @@ export function makeWorld(droneCount: number): World {
|
|||||||
{ id: 0, pos: { x: 340, y: 420 }, radius: 60, moving: false, vel: { x: 0, y: 0 } },
|
{ id: 0, pos: { x: 340, y: 420 }, radius: 60, moving: false, vel: { x: 0, y: 0 } },
|
||||||
{ id: 1, pos: { x: 700, y: 260 }, radius: 45, moving: false, vel: { x: 0, y: 0 } },
|
{ id: 1, pos: { x: 700, y: 260 }, radius: 45, moving: false, vel: { x: 0, y: 0 } },
|
||||||
{ id: 2, pos: { x: 560, y: 720 }, radius: 70, moving: false, vel: { x: 0, y: 0 } },
|
{ id: 2, pos: { x: 560, y: 720 }, radius: 70, moving: false, vel: { x: 0, y: 0 } },
|
||||||
{ id: 3, pos: { x: 200, y: 800 }, radius: 30, moving: true, vel: { x: 14, y: -6 } },
|
spawnTransit(3),
|
||||||
{ id: 4, pos: { x: 820, y: 620 }, radius: 26, moving: true, vel: { x: -10, y: 9 } },
|
spawnTransit(4),
|
||||||
|
spawnTransit(5),
|
||||||
];
|
];
|
||||||
return { t: 0, drones, obstacles, links: new Map(), totalBytes: 0, broadcasts: 0 };
|
return { t: 0, drones, obstacles, links: new Map(), totalBytes: 0, broadcasts: 0 };
|
||||||
}
|
}
|
||||||
@@ -103,12 +144,13 @@ function linkKey(a: number, b: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function steer(d: Drone, world: World, dt: number): Drone {
|
function steer(d: Drone, world: World, dt: number): Drone {
|
||||||
const wp = WAYPOINTS[(d.waypoint + d.id) % WAYPOINTS.length];
|
const route = routeOf(d);
|
||||||
|
const wp = route[(d.waypoint + d.id) % route.length];
|
||||||
let ax = wp.x - d.pos.x;
|
let ax = wp.x - d.pos.x;
|
||||||
let ay = wp.y - d.pos.y;
|
let ay = wp.y - d.pos.y;
|
||||||
const wpDist = Math.hypot(ax, ay);
|
const wpDist = Math.hypot(ax, ay);
|
||||||
let waypoint = d.waypoint;
|
let waypoint = d.waypoint;
|
||||||
if (wpDist < 90) waypoint = (d.waypoint + 1) % WAYPOINTS.length;
|
if (wpDist < 90) waypoint = (d.waypoint + 1) % route.length;
|
||||||
ax /= wpDist || 1;
|
ax /= wpDist || 1;
|
||||||
ay /= wpDist || 1;
|
ay /= wpDist || 1;
|
||||||
|
|
||||||
@@ -157,18 +199,15 @@ function steer(d: Drone, world: World, dt: number): Drone {
|
|||||||
|
|
||||||
export function tick(world: World, dt: number): World {
|
export function tick(world: World, dt: number): World {
|
||||||
const drones = world.drones.map((d) => steer(d, world, dt));
|
const drones = world.drones.map((d) => steer(d, world, dt));
|
||||||
const obstacles = world.obstacles.map((ob) =>
|
const obstacles = world.obstacles.map((ob) => {
|
||||||
ob.moving
|
if (!ob.moving) return ob;
|
||||||
? {
|
const moved = {
|
||||||
...ob,
|
...ob,
|
||||||
pos: { x: ob.pos.x + ob.vel.x * dt, y: ob.pos.y + ob.vel.y * dt },
|
pos: { x: ob.pos.x + ob.vel.x * dt, y: ob.pos.y + ob.vel.y * dt },
|
||||||
vel: {
|
};
|
||||||
x: ob.pos.x < 60 || ob.pos.x > AREA - 60 ? -ob.vel.x : ob.vel.x,
|
// Once a transit object leaves the area, respawn it at a random edge
|
||||||
y: ob.pos.y < 60 || ob.pos.y > AREA - 60 ? -ob.vel.y : ob.vel.y,
|
return outOfTransit(moved) ? spawnTransit(ob.id) : moved;
|
||||||
},
|
});
|
||||||
}
|
|
||||||
: ob,
|
|
||||||
);
|
|
||||||
|
|
||||||
const links = new Map(world.links);
|
const links = new Map(world.links);
|
||||||
let totalBytes = world.totalBytes;
|
let totalBytes = world.totalBytes;
|
||||||
|
|||||||
Reference in New Issue
Block a user