Cut baseline wear to something the parts economy can pay for

Tyres shed 8e-5 per metre, so twelve kilometres of clean driving - no
crashes at all - took them from new to ruined. A two-kilometre round
trip paid ~0.3 parts against ~0.24 of condition spent: driving
perfectly barely broke even, and any knock put you permanently behind.
That is not a decision about how hard to push the car, it is a decline
with no way to arrest it.

At a quarter of the old rates the same trip spends about 0.06, so
roughly two clean runs pay for one bad crash. Enough slack to gamble
with, not enough to ignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dejvino 2026-08-09 07:08:30 +02:00
parent 84ee726a51
commit d19ce2821f
2 changed files with 27 additions and 3 deletions

View File

@ -149,13 +149,23 @@ const MAX_DAMAGE_PER_STEP = 0.25;
*
* Baseline wear is deliberately quicker than real life so decline is legible
* within a session, but it has to stay inside what the parts economy can pay
* for see the reasoning on the two constants above.
* for. The old figures did not: tyres shed 8e-5 per metre, so twelve clean
* kilometres no crashes at all took them from new to ruined, while a
* two-kilometre round trip paid about 0.3 parts against 0.24 of condition
* spent. Driving perfectly barely broke even and one knock put you permanently
* behind, which is a decline with no way to arrest it rather than a decision
* about how hard to push the car.
*
* At a quarter of that a typical mission spends about 0.06 condition against
* ~0.3 parts earned, so roughly two clean runs pay for one bad crash. That is
* the ratio the whole risk/reward loop rests on: enough slack to gamble with,
* not enough to ignore.
*/
export function applyWear(c: CarCondition, w: WearInput): CarCondition {
const impact = w.impactForce / IMPACT_REFERENCE;
const damage: Subsystems = {
engine: w.throttle * w.dt * 6e-4 + impact * 0.32,
tires: w.distance * 8e-5 + impact * 0.5,
engine: w.throttle * w.dt * 1.5e-4 + impact * 0.32,
tires: w.distance * 2e-5 + impact * 0.5,
chassis: impact * 1.0,
};

View File

@ -85,6 +85,20 @@ describe('car condition', () => {
expect(crashes).toBeGreaterThan(5);
});
it('lets a careful driver come out ahead of the parts they are paid', () => {
// Two kilometres at 20 m/s with nothing hit: the shape of an ordinary
// mission. It has to cost distinctly less than the ~0.3 parts one pays, or
// driving perfectly is a losing move and the economy has no floor.
let c = freshCondition();
const steps = Math.round((2000 / 20) * 60);
for (let i = 0; i < steps; i++) {
c = applyWear(c, { dt: 1 / 60, distance: 20 / 60, throttle: 1, impactForce: 0 });
}
const spent = 3 - (c.level.engine + c.level.tires + c.level.chassis);
expect(spent).toBeGreaterThan(0.02);
expect(spent).toBeLessThan(0.12);
});
it('makes a worn car measurably worse to drive', () => {
const fresh = deriveHandling(freshCondition());
const worn = deriveHandling({