Phase 1: road network and heat
Roads are a jittered 7x7 grid with ~a quarter of the edges thinned out, kept connected. The loops are the point: "take a different road" is not a decision without alternative routes. Every metre driven adds heat to that segment; all roads shed it slowly. Crossing a threshold escalates Clear -> Patrol -> Barricade -> Turret, each of which puts real obstructions on the tarmac. Hysteresis stops a road on a boundary from rebuilding its barricade every few seconds. Level changes are deferred while the car is on the segment. Building a barricade around the player would spawn a static collider inside the chassis, and the player is meant to discover escalation by returning to a road, not by watching it assemble behind them. Checkpoint positions are seeded from the segment id, so a stretch of road always fortifies in the same place — recognising it is part of learning the map. Scenery now avoids the tarmac and the car spawns at a junction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,9 @@ const IDLE: DriverInput = { throttle: 0, steer: 0, handbrake: false, respawn: fa
|
||||
|
||||
/** Rapier runs headless, so vehicle tuning is checkable without a browser. */
|
||||
async function run(input: Partial<DriverInput>, seconds: number) {
|
||||
const physics = await createPhysics(generateWorld(1, 0));
|
||||
const world = generateWorld(1, 0);
|
||||
const physics = await createPhysics(world);
|
||||
const start = physics.chassis.translation();
|
||||
const state = createDriveState();
|
||||
const handling = deriveHandling(freshCondition());
|
||||
const cmd = { ...IDLE, ...input };
|
||||
@@ -21,8 +23,10 @@ async function run(input: Partial<DriverInput>, seconds: number) {
|
||||
physics.step(STEP);
|
||||
maxYawRate = Math.max(maxYawRate, Math.abs(physics.chassis.angvel().y));
|
||||
}
|
||||
const now = physics.chassis.translation();
|
||||
return {
|
||||
pos: physics.chassis.translation(),
|
||||
// Displacement from the spawn point, which is a road junction, not the origin.
|
||||
pos: { x: now.x - start.x, y: now.y, z: now.z - start.z },
|
||||
speed: physics.vehicle.currentVehicleSpeed(),
|
||||
maxYawRate,
|
||||
grounded: [0, 1, 2, 3].every((i) => physics.vehicle.wheelIsInContact(i)),
|
||||
|
||||
+31
-2
@@ -13,6 +13,18 @@ export interface PhysicsWorld {
|
||||
drainImpactForce(): number;
|
||||
step(dt: number): void;
|
||||
respawn(): void;
|
||||
/** Static box, added and removed at runtime as road heat rises and falls. */
|
||||
addStaticBox(box: StaticBox): RAPIER.RigidBody;
|
||||
removeBody(body: RAPIER.RigidBody): void;
|
||||
}
|
||||
|
||||
export interface StaticBox {
|
||||
x: number;
|
||||
z: number;
|
||||
yaw: number;
|
||||
width: number;
|
||||
height: number;
|
||||
depth: number;
|
||||
}
|
||||
|
||||
/** Contacts weaker than this are just kerb-scrubbing, not damage. */
|
||||
@@ -55,7 +67,7 @@ export async function createPhysics(model: WorldModel): Promise<PhysicsWorld> {
|
||||
// --- Car chassis ---
|
||||
const chassis = world.createRigidBody(
|
||||
RAPIER.RigidBodyDesc.dynamic()
|
||||
.setTranslation(CAR.spawn.x, CAR.spawn.y, CAR.spawn.z)
|
||||
.setTranslation(model.spawn.x, CAR.spawn.y, model.spawn.z)
|
||||
.setLinearDamping(0.1)
|
||||
.setAngularDamping(0.4)
|
||||
// The mass comes from here, not from collider density, so the centre of mass
|
||||
@@ -127,8 +139,25 @@ export async function createPhysics(model: WorldModel): Promise<PhysicsWorld> {
|
||||
return v;
|
||||
},
|
||||
|
||||
addStaticBox(box) {
|
||||
const body = world.createRigidBody(
|
||||
RAPIER.RigidBodyDesc.fixed()
|
||||
.setTranslation(box.x, box.height / 2, box.z)
|
||||
.setRotation({ x: 0, y: Math.sin(box.yaw / 2), z: 0, w: Math.cos(box.yaw / 2) }),
|
||||
);
|
||||
world.createCollider(
|
||||
RAPIER.ColliderDesc.cuboid(box.width / 2, box.height / 2, box.depth / 2).setFriction(0.8),
|
||||
body,
|
||||
);
|
||||
return body;
|
||||
},
|
||||
|
||||
removeBody(body) {
|
||||
world.removeRigidBody(body);
|
||||
},
|
||||
|
||||
respawn() {
|
||||
chassis.setTranslation({ x: CAR.spawn.x, y: CAR.spawn.y, z: CAR.spawn.z }, true);
|
||||
chassis.setTranslation({ x: model.spawn.x, y: CAR.spawn.y, z: model.spawn.z }, true);
|
||||
chassis.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true);
|
||||
chassis.setLinvel({ x: 0, y: 0, z: 0 }, true);
|
||||
chassis.setAngvel({ x: 0, y: 0, z: 0 }, true);
|
||||
|
||||
Reference in New Issue
Block a user