#124 bench: the ZONE-WALK MATRIX -- systematic per-panel targeting, operator-watchable
The precision rig the operator specified: two nodes, target visible, every zone aimed at deliberately through the REAL reticle/pick path. * BT_SPIN_SELF=<deg/s> (target node): rotates the node's own viewpoint mech in place (the BT_SPAWN_AT write pattern per frame) so every aspect passes the shooter's boresight; the operator watches this node's paper doll take the hits. * BT_ZONE_WALK=<secs/zone> (shooter node): walks the target's damage zones in order, resolves each zone's carrier segment's live world position (BTResolveSegmentWorld + the new BTMechZoneSegAndName bridge), SERVOS the torso twist + aim elevation until the centered reticle ray (BTGetAimRay) points at the segment, fires 3 laser pulses, advances. [walk] ZONE/FIRE/HOLD on A pairs with [dmghit] zone/level lines on B. * scratchpad/night10/zonewalk.sh: launch both + relay, map sed'd to grass/day, NO kill timer -- the session stays up for observation. Hard-won servo constraints (documented in test-harness.md so they are never relearned): the ENGAGE GATE (ray live + range<150 + bearing<1.1; outside it RELAX the twist -- servoing at the twist limit while the goto marches is a limit-clamp fight that visibly shakes the mech, and a 0.55 gate deadlocks against the goto's ~0.55 resting bearing); YAW POLARITY -1 (the twist cell's angular sense is opposite atan2(x,-z) world yaw -- operator-observed live, the SECOND witness for the #124 SelectSlice twist-sign flip) with a divergence watchdog that self-flips; damped correction (gain .40, cap .025/frame -- the aim ray lags the twist write a frame); and a one-shot approach PORT to 100u off the target instead of a cross-map march. First live run: walker cycles zones, settles, fires; the target's luleg climbed 0.458->0.523 under its own aimed pulses -- aimed shot, correct panel, damage consumed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b0b7adc4ea
commit
16e75d6e1a
@@ -90,6 +90,43 @@ own1 snd1 thr1 vul1 (own1 has NO arm zones — rack zones instead).
|
||||
against cavern rock and they shoot terrain for the whole window. Always sed the
|
||||
map/time on the COPY, single-node and two-node alike.
|
||||
|
||||
### THE ZONE-WALK MATRIX (`scratchpad/night10/zonewalk.sh`) — systematic
|
||||
### per-panel hit-location testing, operator-watchable (built for #124)
|
||||
The precision-targeting rig: **two nodes**. Node B (launched first, back
|
||||
window) is the TARGET — `BT_SPIN_SELF=15` rotates its own viewpoint mech in
|
||||
place (deg/s; the BT_SPAWN_AT write pattern, applied per frame) so every
|
||||
aspect passes the shooter's boresight; its window shows its own paper doll
|
||||
taking the hits (`BT_DMG_LOG=1` prints every `[dmghit]` with zone + level).
|
||||
Node A (foreground — it owns the pick focus) runs `BT_ZONE_WALK=<secs/zone>`:
|
||||
walks the target's damage zones in index order, resolves each zone's CARRIER
|
||||
SEGMENT's live world position (`BTResolveSegmentWorld` — the same resolver the
|
||||
attached smoke rides; zone→segment via the `BTMechZoneSegAndName` bridge),
|
||||
SERVOS the torso twist + aim elevation until the centered reticle's pick ray
|
||||
(`BTGetAimRay(0,0)`) points at the segment, then pulses the laser trigger 3×
|
||||
and advances. `[walk] ZONE/FIRE/HOLD` lines on A pair with `[dmghit]` on B.
|
||||
Launch: `bash scratchpad/night10/zonewalk.sh` (relay included; NO kill timer —
|
||||
the session stays up for observation; teardown = taskkill btl4).
|
||||
Companion scalpels: `BT_ASPECT_TEST=1` (the zero-premise frame probe — 4
|
||||
world-cardinal self-impacts + one at each weapon's physical MUZZLE, the
|
||||
left/right anchors that caught the #124 reflection-vs-rotation error);
|
||||
`BT_AIM_SWEEP=<period>` (blind elevation sweep — superseded by the walker).
|
||||
Hard-won constraints baked into the walker (do not relearn these):
|
||||
- **Engage gate**: servo only when the aim ray is live AND range < 150 AND
|
||||
|bearing| < 1.1 rad; otherwise RELAX the twist toward 0 and hold the zone
|
||||
clock. Without it the servo winds the twist to its limit while the goto
|
||||
marches (limit-clamp fight = the mech visibly SHAKES) — and the gate must
|
||||
sit well above the goto's resting bearing error (~0.55 rad at STOP=90) or
|
||||
it deadlocks in HOLD.
|
||||
- **Yaw polarity**: the twist cell's angular sense is OPPOSITE atan2(x,−z)
|
||||
world yaw (operator-observed live: `+=` turned the torso AWAY — the second
|
||||
witness for the #124 SelectSlice twist-sign flip). The servo starts at −1
|
||||
and carries a growth WATCHDOG that self-flips if the error diverges.
|
||||
- **Damped servo**: gain 0.40, step cap 0.025 rad/frame, deadband — the aim
|
||||
ray lags the twist write by a frame; full-error correction oscillates.
|
||||
- **Approach port**: dropzones spawn cross-map; the walker one-shot teleports
|
||||
A to 100 u off the target (BT_SPAWN_AT pattern incl. the +500 y ground-snap
|
||||
lift) instead of minutes of marching.
|
||||
|
||||
### Two-node bench skeleton (the MP pattern)
|
||||
```bash
|
||||
bt_assert_player_env
|
||||
|
||||
@@ -707,6 +707,7 @@ static int gEnemyDestroyed = 0;
|
||||
extern Entity *gEnemyMech;
|
||||
// gauge scoring wave: producers (btplayer.cpp) -- feed the scoreboard from combat.
|
||||
extern void BTPostDamageScore(Entity *victim, Scalar damage, Entity *shooter); // per-hit SCORE (ScoreInflicted)
|
||||
float gBTWalkElev = 0.0f; // #124 zone walker: servo-driven aim elevation (rad)
|
||||
extern void BTPostKillScore(Entity *victim, Scalar damage); // KILL (+ MP death)
|
||||
|
||||
// Mech target slots (verified vs the binary's weapon/fire path, part_013.c):
|
||||
@@ -3807,6 +3808,7 @@ void
|
||||
if (s_spawnAt == 1)
|
||||
{
|
||||
s_spawnAt = 2;
|
||||
// (BT_SPIN_SELF lives right below -- same proven code path)
|
||||
localOrigin.linearPosition.x = s_sx;
|
||||
localOrigin.linearPosition.z = s_sz;
|
||||
// FIX (user-reported: teleport embeds the mech in sloped terrain):
|
||||
@@ -3830,6 +3832,33 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// #124 MATRIX BENCH (BT_SPIN_SELF=<deg/s>): rotate OUR OWN mech in place
|
||||
// -- run on the TARGET node of a two-node zone-walk session, so the
|
||||
// shooter's walker sweeps every aspect while the operator watches this
|
||||
// node's paper doll take the hits. Same write pattern as BT_SPAWN_AT
|
||||
// (heading scalar + quaternion + localToWorld), applied every frame so
|
||||
// nothing fights it.
|
||||
{
|
||||
static float s_ssRate = -2.0f;
|
||||
if (s_ssRate < -1.0f)
|
||||
{
|
||||
const char *e = getenv("BT_SPIN_SELF");
|
||||
s_ssRate = (e && *e) ? (float)atof(e) : -1.0f;
|
||||
if (s_ssRate >= 0.0f)
|
||||
DEBUG_STREAM << "[spin] SELF rotation armed: " << s_ssRate
|
||||
<< " deg/s\n" << std::flush;
|
||||
}
|
||||
if (s_ssRate > 0.0f)
|
||||
{
|
||||
static float s_ssYaw = 0.0f;
|
||||
s_ssYaw += s_ssRate * 0.0174533f * dt;
|
||||
if (s_ssYaw >= 6.2831853f) s_ssYaw -= 6.2831853f;
|
||||
gDriveHeading = s_ssYaw;
|
||||
localOrigin.angularPosition = EulerAngles(0.0f, s_ssYaw, 0.0f);
|
||||
localToWorld = localOrigin;
|
||||
}
|
||||
}
|
||||
|
||||
// BRING-UP repro harness: BT_FORCE_SECONDS=<n> releases the forced throttle
|
||||
// after n sim-seconds (headless stand-in for an interactive tap->release,
|
||||
// exercising the walk->stop gait transition the constant-throttle soaks
|
||||
@@ -5937,7 +5966,27 @@ void
|
||||
{
|
||||
extern float gBTLookPitch, gBTLookYaw;
|
||||
extern Scalar BTGetTorsoElevation(Subsystem *);
|
||||
const float elev = (float)BTGetTorsoElevation(GetTorsoSubsystem());
|
||||
float elev = (float)BTGetTorsoElevation(GetTorsoSubsystem());
|
||||
// #124 ZONE WALKER: the servo owns the aim elevation.
|
||||
if (getenv("BT_ZONE_WALK"))
|
||||
{
|
||||
extern float gBTWalkElev;
|
||||
elev = gBTWalkElev;
|
||||
}
|
||||
// #124 MATRIX BENCH (BT_AIM_SWEEP=<period_s>): triangle-sweep the
|
||||
// aim elevation +-0.20 rad so the pick ray paints the target's
|
||||
// full height through the REAL boresight/pick path.
|
||||
else if (getenv("BT_AIM_SWEEP"))
|
||||
{
|
||||
static float s_swT = 0.0f;
|
||||
float period = (float)atof(getenv("BT_AIM_SWEEP"));
|
||||
if (period < 1.0f) period = 8.0f;
|
||||
s_swT += dt;
|
||||
float ph = fmodf(s_swT, period) / period; // 0..1
|
||||
float tri = (ph < 0.5f) ? (ph * 4.0f - 1.0f)
|
||||
: (3.0f - ph * 4.0f); // -1..1..-1
|
||||
elev = 0.20f * tri;
|
||||
}
|
||||
eyepointRotation = EulerAngles(
|
||||
Radian(Radian::Normalize(gBTLookPitch + elev)),
|
||||
Radian(Radian::Normalize(gBTLookYaw)),
|
||||
@@ -5951,6 +6000,209 @@ void
|
||||
}
|
||||
}
|
||||
|
||||
// #124 ZONE WALKER (BT_ZONE_WALK=<secs-per-zone>): systematic per-panel
|
||||
// targeting through the REAL boresight/pick path. Each period: advance
|
||||
// to the target mech's next damage zone, resolve its CARRIER SEGMENT's
|
||||
// live world position (BTResolveSegmentWorld -- the same resolver the
|
||||
// attached smoke rides), then SERVO the torso twist + aim elevation
|
||||
// until the centered reticle's pick ray (BTGetAimRay(0,0)) points at
|
||||
// the segment, and pulse the laser trigger. Pairs with BT_SPIN_SELF on
|
||||
// the target node: over one revolution every zone is aimed at from
|
||||
// every aspect. [walk] logs each zone/aim/shot; the TARGET node's
|
||||
// [dmghit] answers where the damage actually landed.
|
||||
if ((Entity *)this == application->GetViewpointEntity()
|
||||
&& getenv("BT_ZONE_WALK"))
|
||||
{
|
||||
extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut);
|
||||
extern int BTIsRegisteredMech(Entity *e);
|
||||
extern int BTResolveSegmentWorld(void *entity, int seg_index, float *pos3, float *rows9);
|
||||
extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
|
||||
extern int BTMechZoneSegAndName(void *mech_v, int zone_idx, int *seg_out, const char **name_out);
|
||||
extern Scalar *BTGetTorsoTwistAddr(Subsystem *torso);
|
||||
extern float gBTWalkElev;
|
||||
|
||||
static float s_zwPeriod = 0.0f;
|
||||
static float s_zwT = 1.0e9f;
|
||||
static int s_zwZone = -1;
|
||||
static int s_zwShots = 0;
|
||||
if (s_zwPeriod <= 0.0f)
|
||||
{
|
||||
s_zwPeriod = (float)atof(getenv("BT_ZONE_WALK"));
|
||||
if (s_zwPeriod < 2.0f) s_zwPeriod = 6.0f;
|
||||
}
|
||||
|
||||
Entity *cand[8];
|
||||
int nc = BTGetTargetCandidates((Entity *)this, cand, 8);
|
||||
Mech *target = 0;
|
||||
for (int ci = 0; ci < nc && target == 0; ++ci)
|
||||
if (cand[ci] != 0 && BTIsRegisteredMech(cand[ci]))
|
||||
target = (Mech *)cand[ci];
|
||||
if (target != 0)
|
||||
{
|
||||
// ENGAGE GATE: the servo owns the twist only when the aim ray
|
||||
// is live, the target is inside working range, AND the bearing
|
||||
// sits inside a comfortable twist arc -- the BODY (the goto)
|
||||
// does the coarse facing. Outside the gate: relax the twist
|
||||
// and elevation toward neutral (no limit-clamp fights = no
|
||||
// shaking) and HOLD the zone clock.
|
||||
float sp[3], rows[9], rs[3], rd[3];
|
||||
int rayLive = BTGetAimRay(0.0f, 0.0f, rs, rd);
|
||||
Point3D tp = target->localOrigin.linearPosition;
|
||||
float pdx = (float)(tp.x - localOrigin.linearPosition.x);
|
||||
float pdz = (float)(tp.z - localOrigin.linearPosition.z);
|
||||
float pdist = sqrtf(pdx*pdx + pdz*pdz);
|
||||
// ONE-SHOT APPROACH PORT: cross-map dropzones waste minutes of
|
||||
// march -- teleport to 100 u out, facing the target (the
|
||||
// BT_SPAWN_AT pattern incl. the above-terrain lift), and let
|
||||
// the goto/servo close the rest.
|
||||
static int s_zwPorted = 0;
|
||||
if (!s_zwPorted && pdist > 250.0f)
|
||||
{
|
||||
s_zwPorted = 1;
|
||||
localOrigin.linearPosition.x = tp.x;
|
||||
localOrigin.linearPosition.z = tp.z + 100.0f;
|
||||
localOrigin.linearPosition.y += 500.0f; // ground-snap lift
|
||||
float fy = atan2f((float)(tp.x - localOrigin.linearPosition.x),
|
||||
(float)(tp.z - localOrigin.linearPosition.z));
|
||||
gDriveHeading = fy;
|
||||
localOrigin.angularPosition = EulerAngles(0.0f, fy, 0.0f);
|
||||
localToWorld = localOrigin;
|
||||
DEBUG_STREAM << "[walk] PORTED to 100u off target (was "
|
||||
<< pdist << "u away)\n" << std::flush;
|
||||
}
|
||||
float dx = 0, dy = 0, dz2 = 0, dist = 1.0e9f;
|
||||
if (rayLive)
|
||||
{
|
||||
dx = (float)tp.x - rs[0];
|
||||
dy = ((float)tp.y + 7.0f) - rs[1];
|
||||
dz2 = (float)tp.z - rs[2];
|
||||
dist = sqrtf(dx*dx + dy*dy + dz2*dz2);
|
||||
}
|
||||
float brgErr = 0.0f;
|
||||
if (rayLive && dist > 1.0f)
|
||||
brgErr = atan2f(dx/dist, -dz2/dist) - atan2f(rd[0], -rd[2]);
|
||||
while (brgErr > 3.14159f) brgErr -= 6.2831853f;
|
||||
while (brgErr < -3.14159f) brgErr += 6.2831853f;
|
||||
int engaged = rayLive && dist < 150.0f && fabsf(brgErr) < 1.10f;
|
||||
|
||||
Scalar *tw = BTGetTorsoTwistAddr(GetTorsoSubsystem());
|
||||
if (!engaged)
|
||||
{
|
||||
if (tw != 0) *tw *= 0.96f; // relax, no snapping
|
||||
gBTWalkElev *= 0.96f;
|
||||
s_zwT = 0.0f; // hold the zone clock
|
||||
static float s_zwHold = 0.0f;
|
||||
s_zwHold += dt;
|
||||
if (s_zwHold > 5.0f)
|
||||
{
|
||||
s_zwHold = 0.0f;
|
||||
DEBUG_STREAM << "[walk] HOLD (ray=" << rayLive
|
||||
<< " dist=" << dist << " brgErr=" << brgErr
|
||||
<< ") -- approaching\n" << std::flush;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_zwT += dt;
|
||||
int segIdx = -1;
|
||||
const char *zname = "?";
|
||||
if (s_zwT >= s_zwPeriod || s_zwZone < 0)
|
||||
{
|
||||
// advance to the next zone with a real carrier segment
|
||||
s_zwT = 0.0f;
|
||||
s_zwShots = 0;
|
||||
for (int tries = 0; tries < 64; ++tries)
|
||||
{
|
||||
++s_zwZone;
|
||||
if (!BTMechZoneSegAndName(target, s_zwZone, &segIdx, &zname))
|
||||
{
|
||||
s_zwZone = -1; // wrap
|
||||
continue;
|
||||
}
|
||||
if (segIdx >= 0)
|
||||
break;
|
||||
}
|
||||
DEBUG_STREAM << "[walk] ZONE " << s_zwZone << " '" << zname
|
||||
<< "' seg=" << segIdx << " target="
|
||||
<< target->GetEntityID() << "\n" << std::flush;
|
||||
}
|
||||
if (BTMechZoneSegAndName(target, s_zwZone, &segIdx, &zname)
|
||||
&& segIdx >= 0
|
||||
&& BTResolveSegmentWorld((void *)target, segIdx, sp, rows))
|
||||
{
|
||||
float wx = sp[0]-rs[0], wy = sp[1]-rs[1], wz = sp[2]-rs[2];
|
||||
float wl = sqrtf(wx*wx + wy*wy + wz*wz);
|
||||
if (wl > 1.0f)
|
||||
{
|
||||
wx /= wl; wy /= wl; wz /= wl;
|
||||
float yawErr = atan2f(wx, -wz) - atan2f(rd[0], -rd[2]);
|
||||
while (yawErr > 3.14159f) yawErr -= 6.2831853f;
|
||||
while (yawErr < -3.14159f) yawErr += 6.2831853f;
|
||||
float pitchErr = asinf(wy) - asinf(rd[1]);
|
||||
// DAMPED servo: gain + step cap + deadband (the aim
|
||||
// ray lags the twist write by a frame; full-error
|
||||
// correction under lag oscillates). YAW POLARITY:
|
||||
// the twist cell's angular sense is OPPOSITE the
|
||||
// atan2(x,-z) world-yaw convention (operator-
|
||||
// observed: += turned the torso AWAY and wound the
|
||||
// limit -- the same reversed sense the #124 adapter
|
||||
// flags for SelectSlice's twist term). Start -1;
|
||||
// the growth WATCHDOG flips the latch if the error
|
||||
// diverges ~25 straight frames, so a wrong polarity
|
||||
// self-corrects instead of limit-shaking.
|
||||
static float s_zwYawPol = -1.0f;
|
||||
static float s_zwPrevYaw = -1.0f;
|
||||
static int s_zwGrow = 0;
|
||||
const float kGain = 0.40f, kCap = 0.025f;
|
||||
if (tw != 0 && fabsf(yawErr) > 0.004f)
|
||||
{
|
||||
float st = kGain * yawErr;
|
||||
if (st > kCap) st = kCap;
|
||||
if (st < -kCap) st = -kCap;
|
||||
*tw += s_zwYawPol * st;
|
||||
if (s_zwPrevYaw >= 0.0f
|
||||
&& fabsf(yawErr) > s_zwPrevYaw + 1.0e-4f)
|
||||
{
|
||||
if (++s_zwGrow > 25)
|
||||
{
|
||||
s_zwYawPol = -s_zwYawPol;
|
||||
s_zwGrow = 0;
|
||||
DEBUG_STREAM << "[walk] yaw polarity flipped to "
|
||||
<< s_zwYawPol << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
s_zwGrow = 0;
|
||||
}
|
||||
s_zwPrevYaw = fabsf(yawErr);
|
||||
}
|
||||
if (fabsf(pitchErr) > 0.003f)
|
||||
{
|
||||
float es = kGain * pitchErr;
|
||||
if (es > 0.015f) es = 0.015f;
|
||||
if (es < -0.015f) es = -0.015f;
|
||||
gBTWalkElev += es;
|
||||
if (gBTWalkElev > 0.40f) gBTWalkElev = 0.40f;
|
||||
if (gBTWalkElev < -0.40f) gBTWalkElev = -0.40f;
|
||||
}
|
||||
int aligned = (fabsf(yawErr) < 0.012f
|
||||
&& fabsf(pitchErr) < 0.010f);
|
||||
if (aligned && s_zwShots < 3
|
||||
&& s_zwT > 0.30f * s_zwPeriod + s_zwShots * 1.2f)
|
||||
{
|
||||
gBTLaserKey = 1;
|
||||
++s_zwShots;
|
||||
DEBUG_STREAM << "[walk] FIRE " << s_zwShots
|
||||
<< " zone " << s_zwZone << " '" << zname
|
||||
<< "' range=" << wl << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// task #6 ORDER FIX: the scripted block must run BEFORE the fire-push
|
||||
// block below -- it writes gBTLaserKey/gBTConfigKey, which the earlier
|
||||
// keyboard poll zeroes each frame while the window is unfocused; the
|
||||
|
||||
@@ -1438,3 +1438,22 @@ void BTAmmoExplosionFanOut(
|
||||
((Mech *)owner_mech)->AmmoExplosionFanOut(
|
||||
(::Subsystem *)inflicting_subsystem, inflicting_subsystem_id, total_damage);
|
||||
}
|
||||
|
||||
//
|
||||
// #124 ZONE WALKER bridge: a zone's carrier SEGMENT index + name, complete-
|
||||
// type TU (Mech__DamageZone). Replicant-safe -- the zone table streams on
|
||||
// every instance, so the SHOOTER can enumerate its target's zones locally.
|
||||
// Returns 0 past the end (the walker wraps on that).
|
||||
//
|
||||
int BTMechZoneSegAndName(void *mech_v, int zone_idx, int *seg_out, const char **name_out)
|
||||
{
|
||||
Mech *m = (Mech *)mech_v;
|
||||
if (m == 0 || zone_idx < 0 || zone_idx >= m->damageZoneCount)
|
||||
return 0;
|
||||
Mech__DamageZone *dz = m->Zone(zone_idx);
|
||||
if (dz == 0)
|
||||
return 0;
|
||||
*seg_out = dz->EffectSegmentIndex(); // public accessor (@0x194)
|
||||
*name_out = (const char *)dz->damageZoneName; // engine base, public
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# #124 ZONE-WALK MATRIX -- the systematic per-panel targeting session (2-node,
|
||||
# operator-watchable). Node B (right window) = the TARGET: spins in place
|
||||
# (BT_SPIN_SELF), its paper doll shows every hit landing. Node A = the
|
||||
# SHOOTER: BT_ZONE_WALK servo-aims the real boresight at each of B's zones'
|
||||
# carrier segments in turn, settles, fires 3 laser pulses, advances. One
|
||||
# B-revolution per ~24 s x 6 s/zone => every zone gets aimed shots from many
|
||||
# aspects over a few minutes.
|
||||
# READ: A's log [walk] ZONE/FIRE lines vs B's log [dmghit] zone= lines.
|
||||
# The session STAYS UP for observation -- close the windows (or taskkill
|
||||
# btl4.exe) when done; the relay dies with this script's 60-min tail.
|
||||
set -x
|
||||
. /c/git/bt411/scratchpad/night6/bench_common.sh
|
||||
cd /c/git/bt411/content || exit 1
|
||||
bt_assert_player_env
|
||||
taskkill //F //IM btl4.exe > /dev/null 2>&1
|
||||
sleep 2
|
||||
rm -f zw_a.log zw_b.log
|
||||
bt_expert_egg MP.EGG ZW.EGG
|
||||
sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; 0,/^vehicle=.*/s//vehicle=madcat/" ZW.EGG
|
||||
|
||||
# TARGET first (right/back window): spins, logs every damage application.
|
||||
BT_SPIN_SELF=15 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_MP_LOG=1 \
|
||||
bt_launch zw_b.log ZW.EGG 0x0C -net 1601
|
||||
sleep 2
|
||||
# SHOOTER second (foreground -- owns the pick focus): the walker.
|
||||
BT_ZONE_WALK=6 BT_GOTO=enemy BT_GOTO_STOP=90 BT_KEY_NOFOCUS=1 \
|
||||
BT_DMG_LOG=1 BT_RANGE_LOG=1 BT_MP_LOG=1 \
|
||||
bt_launch zw_a.log ZW.EGG 0x03 -net 1501
|
||||
sleep 5
|
||||
python ../tools/btconsole.py ZW.EGG 127.0.0.1:1501 127.0.0.1:1601 > zw_relay.log 2>&1 &
|
||||
sleep 3600 # keep the relay's parent alive for the observation hour
|
||||
Reference in New Issue
Block a user