Tune escort encounter pacing to a hot lane cadence

- Replace near-certain short-interval rolls with a middling 40–60% chance on a 20–30s interval and a longer 15–30s cooldown, targeting roughly 2–8 engagements per run
- Update the escort director comments in sidequests.json and GameScene.js to describe the new tuning
This commit is contained in:
Brian Fertig 2026-09-23 21:51:33 -06:00
parent 5810560b18
commit c1c4d0e22c
2 changed files with 11 additions and 11 deletions

View File

@ -200,7 +200,7 @@
}
},
"escort": {
"_comment": "The ESCORT category's rules (the third live type — the model: js/quests/SideQuests.js → escortDestinationFor / deriveEscortPlan; the ship: js/combat/TransportShip.js + data/combat.json → transport; the scene: GameScene._establishEscort + updateEscort). An escort is a CONVOY JOB: a neutral TRANSPORT (its own ship class — ~2.5x the fighter's size, far heavier armor, a heavy lance) plus 1–2 escort fighters stands at a single RENDEZVOUS waypoint in the posting's own system (waypoints [1,1] — the route builder's shared placement law, the 'escort' fork). The player flies to the waypoint within visitRadius and the convoy LINKS (phase 'rendezvous' → 'transit') — from then on the transport + fighters hold their escort ring around the player through EVERY remaining leg, re-forming across jumps (the route record carries the transport's armor — a signed contract is a signed contract: a jump or a save never re-rolls a standing convoy). The DESTINATION is a planet or a free-space station 1–2 gate hops away (destinationHops [min,max], a seeded walk of the jump network — js/galaxy/JumpTravel.js's gate graph, the same links the player can actually fly), fixed at the accept and never re-rolled; the player flies the legs (1–2 jumps) and arrives at the destination object with the convoy intact. ENCOUNTERS — the lane is HOT: while the convoy is linked, the escort director (its own EncounterDirector instance with these tighter numbers — a shorter roll interval, a near-certain chance, a short quiet) fires WAVES of mixed fighter types (bands, by the player's combat level — a weighted roster of enemyTypes ids, validated like the patrol's) with LARGER concurrent counts than a normal encounter (wave.total / maxConcurrent) — one at a time, the wave run's completion the quiet's gate, exactly the patrol contact's law. The TRANSPORT is the job: it fights alongside (its own lance, js/combat/TransportShip.js), it is a legal enemy target (the allies roster — pickEnemyTarget's escort aggro), and if it is DESTROYED the contract fails (phase 'failed', the convoy breaks off) — the player must LAND ON A PLANET to reset it (GameScene.startLanding's escort reset — a fresh rendezvous, a fresh transport, the same destination: retry the run). COMPLETION — the destination world (planet/free-space station) is VISIBLE ON SCREEN (circleInView against the camera's world rect — the player has flown it into view, not merely near it) with NO enemy fighter up (the craftVisible roster rule — the bounty field-clear's law): the contract enters its DELIVERY (phase 'delivering') — the convoy BREAKS OFF the wing, flies in beside the world, and FADES OUT (the touchdown: the transport + its fighters land, they are through, not destroyed; a fixed time-driven fly-in then fade, GameScene._runEscortDelivery), and on the fade's close the contract completes, the reward stands ready to claim. compass + marker = the escort's in-game face: the green next-stop arrow (the RENDEZVOUS waypoint pre-link, then the DESTINATION gate, then the destination object itself) and the in-world beacons (rendezvous = pulsing until linked, then the destination object's mark).",
"_comment": "The ESCORT category's rules (the third live type — the model: js/quests/SideQuests.js → escortDestinationFor / deriveEscortPlan; the ship: js/combat/TransportShip.js + data/combat.json → transport; the scene: GameScene._establishEscort + updateEscort). An escort is a CONVOY JOB: a neutral TRANSPORT (its own ship class — ~2.5x the fighter's size, far heavier armor, a heavy lance) plus 1–2 escort fighters stands at a single RENDEZVOUS waypoint in the posting's own system (waypoints [1,1] — the route builder's shared placement law, the 'escort' fork). The player flies to the waypoint within visitRadius and the convoy LINKS (phase 'rendezvous' → 'transit') — from then on the transport + fighters hold their escort ring around the player through EVERY remaining leg, re-forming across jumps (the route record carries the transport's armor — a signed contract is a signed contract: a jump or a save never re-rolls a standing convoy). The DESTINATION is a planet or a free-space station 1–2 gate hops away (destinationHops [min,max], a seeded walk of the jump network — js/galaxy/JumpTravel.js's gate graph, the same links the player can actually fly), fixed at the accept and never re-rolled; the player flies the legs (1–2 jumps) and arrives at the destination object with the convoy intact. ENCOUNTERS — the lane is HOT: while the convoy is linked, the escort director (its own EncounterDirector instance — a short roll interval every 20–30 s at a middling chance, a long quiet) fires WAVES of mixed fighter types (bands, by the player's combat level — a weighted roster of enemyTypes ids, validated like the patrol's) with LARGER concurrent counts than a normal encounter (wave.total / maxConcurrent) — one at a time, the wave run's completion the quiet's gate, exactly the patrol contact's law. The TRANSPORT is the job: it fights alongside (its own lance, js/combat/TransportShip.js), it is a legal enemy target (the allies roster — pickEnemyTarget's escort aggro), and if it is DESTROYED the contract fails (phase 'failed', the convoy breaks off) — the player must LAND ON A PLANET to reset it (GameScene.startLanding's escort reset — a fresh rendezvous, a fresh transport, the same destination: retry the run). COMPLETION — the destination world (planet/free-space station) is VISIBLE ON SCREEN (circleInView against the camera's world rect — the player has flown it into view, not merely near it) with NO enemy fighter up (the craftVisible roster rule — the bounty field-clear's law): the contract enters its DELIVERY (phase 'delivering') — the convoy BREAKS OFF the wing, flies in beside the world, and FADES OUT (the touchdown: the transport + its fighters land, they are through, not destroyed; a fixed time-driven fly-in then fade, GameScene._runEscortDelivery), and on the fade's close the contract completes, the reward stands ready to claim. compass + marker = the escort's in-game face: the green next-stop arrow (the RENDEZVOUS waypoint pre-link, then the DESTINATION gate, then the destination object itself) and the in-world beacons (rendezvous = pulsing until linked, then the destination object's mark).",
"enabled": true,
"minLevel": 3,
"contexts": [
@ -224,14 +224,14 @@
3
],
"encounters": {
"_comment": "The escort's LANE COMBAT (the director's numbers + the wave's shape + the roster's bands): rollIntervalMin/Max + chanceMin/Max + cooldownMin/Max are the EscortDirector's own EncounterDirector config (data/combat.json → encounters' shape — seconds); wave = one rolling wave of `total` mixed fighters, `maxConcurrent` on screen at a time, each kill replaced (replacePerKill) — the patrol contact's rolling shape, bigger; bands = the roster by the player's COMBAT level (levels [lo,hi], roster = a weighted { type: weight } table over combat.enemyTypes ids — the unknown-id rule is the patrol's: a type that isn't in combat.enemyTypes never ships, a roster that empties takes the established type); no band standing → the fallback band's roster.",
"_comment": "The escort's LANE COMBAT (the director's numbers + the wave's shape + the roster's bands): rollIntervalMin/Max + chanceMin/Max + cooldownMin/Max are the EscortDirector's own EncounterDirector config (data/combat.json → encounters' shape — seconds), tuned to a HOT lane: a short 20–30 s roll interval × a middling chance, a 15–30 s quiet after each fight — frequent engagements while the convoy is linked (roughly 2–8 per run, scaling with how long the legs take); wave = one rolling wave of `total` mixed fighters, `maxConcurrent` on screen at a time, each kill replaced (replacePerKill) — the patrol contact's rolling shape, bigger; bands = the roster by the player's COMBAT level (levels [lo,hi], roster = a weighted { type: weight } table over combat.enemyTypes ids — the unknown-id rule is the patrol's: a type that isn't in combat.enemyTypes never ships, a roster that empties takes the established type); no band standing → the fallback band's roster.",
"enabled": true,
"rollIntervalMin": 9,
"rollIntervalMax": 18,
"chanceMin": 0.85,
"chanceMax": 1,
"cooldownMin": 5,
"cooldownMax": 10,
"rollIntervalMin": 20,
"rollIntervalMax": 30,
"chanceMin": 0.4,
"chanceMax": 0.6,
"cooldownMin": 15,
"cooldownMax": 30,
"wave": {
"kind": "rolling",
"total": 8,

View File

@ -1130,9 +1130,9 @@ export class GameScene extends Phaser.Scene {
// AI streams them in).
this.encounters = new EncounterDirector(config.get('combat.encounters', {}));
// THE ESCORT LANE'S DIRECTOR (data/sidequests.json → escort.encounters —
// tighter numbers than the normal director: a shorter roll interval,
// a near-certain chance, a short quiet). It owns no ships either: on a
// hit it's just a "go", and updateEscort fires the escort's mixed-fighters
// a HOT lane: a short 20–30 s roll interval at a middling chance, a
// 15–30 s quiet after each fight). It owns no ships either:
// on a hit it's just a "go", and updateEscort fires the escort's mixed-fighters
// wave (escortWaveRoster's roster, bigger concurrent counts). One at a
// time (the wave run's completion is the quiet's gate — the patrol's law).
this.escortDirector = new EncounterDirector(config.get('sidequests.escort.encounters', {}));