All investigator claims re-verified byte-exact against `section_dump.txt` (my own capstone scripts: `C:\git\bt411\scratchpad\dis_4b2980.py`, `C:\git\bt411\scratchpad\dis_range.py`). Constants confirmed from the dump: `0x4b2d80=0.0f, 0x4b2d84=0.5f, 0x4b2d88=1.3f (0x3FA66666)`, `0x4ab170=0.5f, 0x4ab174=-1.1f, 0x4ab178=0.0f`, `0x4bc3f4=3.0f, 0x4bc3f8=0.0625f`. No disagreements between investigators survived re-reading; one nuance sharpened below (the two random-sign idioms differ: fan-out `jb` = "first roll < 0.5 → negative value roll"; gait jolt = "value roll first, negate if SECOND roll > 0.5 strict").

# IMPLEMENTATION PLAN — damage→gyro fan-out (FUN_004b2980) + the direct crunch kicks

## 0. Prerequisite header fixes (do these first)

**(F1) `C:\git\bt411\game\reconstructed\gyro.hpp:323-325`** — the binary reads `gyro+0x390..0x398` as ONE `Vector3D` (disasm @4b2d2b: `lea eax,[ebx+0x390]` then loads `[eax]/[eax+4]/[eax+8]` as the 3rd call's direction). Replace the three scalars:
```cpp
		// @0x390 (this[0xE4..0xE6]) -- the VIBRATION AXIS: ctor init (0,1,0) =
		// straight up.  Read as one Vector3D by ApplyDamageResponse @4b2d2b
		// (the second ApplyDamageImpulse = the vibration shake).  [T1]
		// (Was mis-split as scalars animationOffset/animationScale/animationPhase.)
		Vector3D	vibrationDirection;		// @0x390  init (0.0f, 1.0f, 0.0f)
```
and `gyro.cpp:309-311` →
```cpp
	vibrationDirection = Vector3D(0.0f, 1.0f, 0.0f);				// @0x390 (0,1,0 = up)
```
Add to the lock block (gyro.cpp:~180): `static_assert(offsetof(Gyroscope, vibrationDirection) == 0x390, "vibrationDirection @0x390");` (no other code reads the old names — grep-verified).

**(F2) `C:\git\bt411\game\reconstructed\mech.hpp:662`** — `int clipLoadGuard; // @0x5c4` is used by the binary as a FLOAT rumble-period timer (`fld [ebx+0x5c4]; fcomp 0.0` @4aa2eb, `fsubr` @4aa359, stores `0x3ecccccd` @4aa347). Re-type/rename:
```cpp
		Scalar			gyroRumbleTimer;		// @0x5c4  binary: float rumble-period countdown
												//         (engaged-gait rumble @4aa2eb-4aa35f);
												//         reset to 0 at clip load (mech3.cpp:330)
```
Update the only other writer, `mech3.cpp:330`: `gyroRumbleTimer = 0.0f;` (bit-identical to the old int 0 write).

## 1. `Gyroscope::ApplyDamageResponse` — the byte-exact FUN_004b2980 body

**Decl** (`gyro.hpp`, after `ApplyVerticalImpulse` at :214; add `class Damage;` fwd-decl at the top if not in scope):
```cpp
		// @004b2980 -- the damage->gyro fan-out.  The binary passes the whole
		// Damage BY VALUE (12 dwords, caller add esp,0x34) but never mutates it
		// and never reads surfaceNormal/impactPoint -- const-ref is byte-equal.
		void	ApplyDamageResponse(const Damage &damage);
```

**Body** (`gyro.cpp`, place directly after `ApplyVerticalImpulse` at :729). Engine ops are the authentic implementations: `Close_Enough(Vector3D,Vector3D,e)` == FUN_004084fc, `Vector3D::Normalize` == FUN_004087f4, `Quaternion::operator=(EulerAngles)` == FUN_00409a00, `AffineMatrix::operator=(Origin)` == FUN_0040ab44 (placement `{pos@+0, quat@+0xC}` IS the engine `Origin` layout, ORIGIN.h:15-16), the mechrecon `FUN_00408744` row-dot (verified in IntegrateMotion) == the dir rotate. `AffineMatrix` is exactly `Scalar entries[12]` (AFFNMTRX.h:21) and `ReconMatrix` wraps only it, so the casts below are layout-exact (lock them):

```cpp
	// TU-local layout locks for the casts in ApplyDamageResponse:
	static_assert(sizeof(AffineMatrix) == 12 * sizeof(Scalar), "AffineMatrix == 12 Scalars");
	static_assert(sizeof(ReconMatrix)  == sizeof(AffineMatrix), "ReconMatrix is a bare AffineMatrix");

//
// @004b2980 [CONFIDENT -- re-disassembled byte-exact] -- the damage->gyro
// fan-out: normalize the hit direction (RANDOM horizontal if ~zero), rotate it
// by the yaw-only torso-twist frame, scale by the per-damage-type
// multiplier/response curves, clamp at 1.3, fire the four Apply* kicks.
//
void
	Gyroscope::ApplyDamageResponse(const Damage &damage)
{
	// @4b298c: zero damage no-ops (const 0.0f @0x4b2d80)
	if (damage.damageAmount == 0.0f)
		return;
	// @4b299e: Collision(0) NEVER bounces via this path (the jump-table case 0
	// that zeroes the responses @4b2b24 is dead code behind this early return)
	if (damage.damageType == Damage::CollisionDamageType)
		return;

	Scalar trans     = 0.0f;					// ebp-0x10
	Scalar pitchRoll = 0.0f;					// ebp-0x0C
	Scalar yaw       = 0.0f;					// ebp-0x08
	Scalar vibration = 0.0f;					// ebp-0x04

	// @4b29bd: FUN_004084fc(damageForce, ZeroVector@0x4e0f74, 1e-4f@0x38d1b717).
	// A ~zero force rolls a RANDOM horizontal direction: per component, a SIGN
	// roll first (jb @4b29eb: first roll < 0.5f@0x4b2d84 -> the value roll is
	// negated), y = 0.  RNG = FUN_00408050 (stream 0x521f5c) == RandomUnit().
	Vector3D dir;								// ebp-0x1C
	if (Close_Enough(damage.damageForce, Vector3D(0.0f, 0.0f, 0.0f), 1e-4f))
	{
		dir.x = (RandomUnit() >= 0.5f) ? RandomUnit() : -RandomUnit();	// @4b29d7
		dir.z = (RandomUnit() >= 0.5f) ? RandomUnit() : -RandomUnit();	// @4b2a0d
		dir.y = 0.0f;													// @4b2a43
	}
	else
	{
		dir = damage.damageForce;				// @4b2a4a FUN_00408440
	}
	dir.Normalize(dir);							// @4b2a62 FUN_004087f4 (unguarded in the binary too)

	// @4b2a67-4b2ac2: yaw-only body frame from the torso twist.
	//   placeRot = (0, *externalPitchPtr, 0)        gyro+0x2A8/0x2AC/0x2B0
	//   placeQuat = QuatFromRotation(placeRot)      FUN_00409a00
	//   placePos  = Zero                            @4b2aa8
	//   workMatrix = MatrixFromPlacement            FUN_0040ab44 (== Origin assign)
	placeRot.y = *externalPitchPtr;				// *(gyro+0x258) = torso currentTwist
	placeRot.x = 0.0f;
	placeRot.z = 0.0f;
	Origin frame;
	frame.angularPosition = EulerAngles(
		Radian(placeRot.x), Radian(placeRot.y), Radian(placeRot.z));	// FUN_00409a00
	placeQuat[0] = frame.angularPosition.x;		// member-state fidelity @0x298
	placeQuat[1] = frame.angularPosition.y;
	placeQuat[2] = frame.angularPosition.z;
	placeQuat[3] = frame.angularPosition.w;
	placePos = Vector3D(0.0f, 0.0f, 0.0f);
	frame.linearPosition = Point3D(placePos.x, placePos.y, placePos.z);
	*(AffineMatrix *)workMatrix = frame;		// @4b2ac2 FUN_0040ab44

	// @4b2ac7-4b2af5: rotate the WORLD hit direction into the torso/body frame
	// (FUN_00408744 row-dot: out[i] = sum_j v[j]*M(i,j)) + re-normalize.
	Vector3D tmp = dir;							// ebp-0x28
	FUN_00408744(&dir, (const Scalar *)&tmp, (ReconMatrix *)workMatrix);
	dir.Normalize(dir);							// @4b2aed

	// @4b2afd: per-type scaling (jump table @0x4b2b10; type > 4 leaves all four
	// zero).  Each term computed INDEPENDENTLY as amount / multiplier * response
	// (same expression shape as the binary's fld/fdiv/fmul per term).
	switch (damage.damageType)
	{
	case Damage::BallisticDamageType:			// @4b2b3d  mult@0x330 resp@0x350
		trans     = damage.damageAmount / damageMultiplier[1] * damageResponse[1].trans;
		pitchRoll = damage.damageAmount / damageMultiplier[1] * damageResponse[1].pitchRoll;
		yaw       = damage.damageAmount / damageMultiplier[1] * damageResponse[1].yaw;
		vibration = damage.damageAmount / damageMultiplier[1] * damageResponse[1].vibration;
		break;
	case Damage::ExplosiveDamageType:			// @4b2b8a  the ONLY case reading burstCount (fild first)
		trans     = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].trans;
		pitchRoll = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].pitchRoll;
		yaw       = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].yaw;
		vibration = (Scalar)damage.burstCount * damage.damageAmount / damageMultiplier[2] * damageResponse[2].vibration;
		break;
	case Damage::LaserDamageType:				// @4b2be3  mult@0x338 resp@0x370
		trans     = damage.damageAmount / damageMultiplier[3] * damageResponse[3].trans;
		pitchRoll = damage.damageAmount / damageMultiplier[3] * damageResponse[3].pitchRoll;
		yaw       = damage.damageAmount / damageMultiplier[3] * damageResponse[3].yaw;
		vibration = damage.damageAmount / damageMultiplier[3] * damageResponse[3].vibration;
		break;
	case Damage::EnergyDamageType:				// @4b2c2d  mult@0x33C resp@0x380
		trans     = damage.damageAmount / damageMultiplier[4] * damageResponse[4].trans;
		pitchRoll = damage.damageAmount / damageMultiplier[4] * damageResponse[4].pitchRoll;
		yaw       = damage.damageAmount / damageMultiplier[4] * damageResponse[4].yaw;
		vibration = damage.damageAmount / damageMultiplier[4] * damageResponse[4].vibration;
		break;
	default:
		break;
	}

	// @4b2c75-4b2ce2: upper-clamp each at 1.3f (0x3FA66666 @0x4b2d88); NO lower clamp.
	if (trans     > 1.3f)	trans     = 1.3f;
	if (pitchRoll > 1.3f)	pitchRoll = 1.3f;
	if (yaw       > 1.3f)	yaw       = 1.3f;
	if (vibration > 1.3f)	vibration = 1.3f;

	// The four kicks.  Call 3 is the vibration SHAKE along the fixed member
	// axis vibrationDirection@0x390 (up); call 4 feeds the response row's
	// third field ("yaw") into the pitch-only vertical impulse.
	ApplyDamageImpulse  (dir.x, dir.y, dir.z, trans);		// @4b2d00
	ApplyDamageTorque   (dir.x, dir.y, dir.z, pitchRoll);	// @4b2d23
	ApplyDamageImpulse  (vibrationDirection.x, vibrationDirection.y,
						 vibrationDirection.z, vibration);	// @4b2d4b
	ApplyVerticalImpulse(dir.x, dir.y, dir.z, yaw);			// @4b2d6e
}
```
(If `Origin` isn't pulled in via `bt.hpp`, add `#include <ORIGIN.hpp>` to gyro.cpp's include block. `RandomUnit` comes via mech.hpp→mechrecon.hpp:104, backed by the engine `RandomGenerator` == FUN_00408050's contract.)

**Bridges** (gyro.cpp, after `GyroBindExternalPitch` at :908 — the databinding-trap pattern; Gyroscope is complete only in this TU):
```cpp
//===========================================================================//
// Damage->gyro bridges (binary call sites: hub @4a02fb; performance crunches
// @4aa254/@4aa288/@4aa342/@4aa81e/@4aa86c; weapon recoil @4bc194).
//===========================================================================//
void GyroApplyDamage(Subsystem *gyro, const Damage &damage)
{
	if (gyro != 0)
		static_cast<Gyroscope *>(gyro)->ApplyDamageResponse(damage);
}
void GyroApplyDamageImpulse(Subsystem *gyro, Scalar x, Scalar y, Scalar z, Scalar magnitude)
{
	if (gyro != 0)
		static_cast<Gyroscope *>(gyro)->ApplyDamageImpulse(x, y, z, magnitude);
}
void GyroApplyDamageTorque(Subsystem *gyro, Scalar x, Scalar y, Scalar z, Scalar magnitude)
{
	if (gyro != 0)
		static_cast<Gyroscope *>(gyro)->ApplyDamageTorque(x, y, z, magnitude);
}
```

## 2. Port call sites

### (a) Weapon-damage path — `mech.cpp` TakeDamageMessageHandler
- **extern** at `mech.cpp:219` (next to the existing gyro externs :217-218): `extern void GyroApplyDamage(Subsystem *, const Damage &);`
- **insert at mech.cpp:~596** — after the `Check(message)`/MP-diag block, BEFORE the `lastInflictingID` write at :602 (binary order: gyro @4a0264 precedes the EntityID assign @4a0327, the threat feed @4a039c and zone resolution @4a037a):
```cpp
	// Binary @0x4a0264-0x4a0300 (hub FUN_004a0230): cockpit hit-BOUNCE.  Feed
	// the gyro the raw Damage record FIRST -- before inflictor bookkeeping,
	// threat feed and zone resolution (an invalid-zone hit still bounces).
	// Sole gate: non-null gyro (mech+0x528).  The fan-out itself no-ops
	// CollisionDamageType(0) and damageAmount==0 (@4b298c/@4b299e); on a
	// replicant the binary only WARNS ("Replicant Mech recieving
	// takedamagemessage!", MECH.CPP:986) and proceeds -- no replicant gate.
	if (gyroSubsystem != 0)
		GyroApplyDamage(gyroSubsystem, message->damageData);
```
This covers aimed AND unaimed hits and the collision TakeDamage messages (type 0 correctly no-ops inside).

### (b) Crushable-icon crunch — `mech4.cpp:3310` (replace the dormant comment inside the `dmg.damageAmount == 0.00123f` branch, after `dmg.damageAmount = 0.0f;` at :3309, before the GroundLog)
```cpp
		// Binary @4aa7ce-4aa871: the gyro CRUNCH.  n = Normalize(dmg.damageForce)
		// (FUN_004087f4 @4aa7ea; damageForce = the delta-v across the bounce,
		// written by engine Mover::ProcessCollisionList, MOVER.cpp:1299 -- no
		// new plumbing), then torque along n @ 0.4 (@4aa81e) + an upward
		// impulse @ 0.2 (@4aa86c).  Normalize is UNGUARDED in the binary too.
		if (gyroSubsystem != 0)
		{
			Vector3D n;
			n.Normalize(dmg.damageForce);
			GyroApplyDamageTorque (gyroSubsystem, n.x, n.y, n.z, 0.4f);	 // @4aa81e 0x3ecccccd
			GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 1.0f, 0.0f, 0.2f); // @4aa86c 0x3e4ccccd
		}
```
externs near mech4.cpp's existing gyro extern (:3166 style, or file-scope): `extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);` / `...GyroApplyDamageTorque(...)`. **Do NOT add any kick to the blocking-hit branch (mech4.cpp:3315+)** — verified no gyro calls in @4aa88a-4aab5f.

### (c) Alternate-gait engage jolt + engaged-gait rumble — `mech4.cpp`, immediately before the channel-advance pair at :2056-2058 (`adv = AdvanceBodyAnimation(dt, 1); legAdv = AdvanceLegAnimation(dt);` — the binary runs @4aa158-4aa365 directly before its IsDisabled + AdvanceLegAnimation sequence @4aa365-4aa399):
```cpp
						// ===== Binary @4aa158-4aa2be: alternate-gait toggle + ENGAGE JOLT =====
						// [T3 NAMING: the gate is (cycling && speedDemand < 0) -- by the port's
						// own sign convention 0x3f4 reads as a REVERSE-gait select; the
						// airborneSelect/airborneCycleRate names are kept but suspect.]
						if (airborneCycleRate != -1.1f)			// sentinel @0x4ab174 (0xbf8ccccd)
						{
							if (legCycleSpeed > 0.0f			// @4aa16d
								&& s_realControls && controlsMapper != 0
								&& controlsMapper->speedDemand < 0.0f)	// @4aa182
							{
								if (airborneSelect == 0)		// rising edge @4aa197
								{
									airborneSelect = 1;
									RequestActionFlags(0x100);	// @4aa1b5 FUN_004a4c54
									forwardCycleRate = airborneCycleRate;	// @4aa1bd
									if (gyroSubsystem != 0)		// @4aa1c9
									{
										Scalar r = RandomUnit();	// @4aa1dc value roll
										if (RandomUnit() > 0.5f)	// @4aa1f6 sign roll (STRICT >, 0.5 @0x4ab170)
											r = -r;
										// dir = (0,0,r): Z-axis, random magnitude+sign; SAME vec both calls
										GyroApplyDamageTorque (gyroSubsystem, 0.0f, 0.0f, r, 0.4f);	// @4aa254
										GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 0.0f, r, 0.4f);	// @4aa288
									}
								}
							}
							else if (airborneSelect != 0)		// falling edge @4aa292
							{
								airborneSelect = 0;
								RequestActionFlags(0x100);
								forwardCycleRate = groundCycleRate;	// @4aa2b2
							}
						}
						// ===== Binary @4aa2be-4aa365: engaged-gait RUMBLE (0.4s period) =====
						if (airborneSelect != 0
							&& legCycleSpeed > walkStrideLength	// @4aa2cc vs @0x534
							&& gyroSubsystem != 0)
						{
							if (gyroRumbleTimer > 0.0f)			// @4aa2eb
								gyroRumbleTimer -= dt;			// @4aa356 fsubr
							else
							{
								GyroApplyDamageImpulse(gyroSubsystem, 0.0f, 1.0f, 0.0f, 0.2f);	// @4aa342 up, 0x3e4ccccd
								gyroRumbleTimer = 0.4f;			// @4aa347 0x3ecccccd
							}
						}
```
Note: this block is also the port's first authentic WRITER of `airborneSelect` (previously never set). All gate members exist in mech.hpp (:641-657, :695).

### (d) Firing recoil kick — `projweap.cpp:603` `ProjectileWeapon::FireWeapon`, insert after `Check(this)` at :605, BEFORE `ConsumeRound()` at :608 (binary: heat add @4bc11e → kick @4bc136-4bc199 → tracer/spawn; no early-out precedes the kick):
```cpp
	// Binary @4bc136-4bc19c: firing RECOIL kick, before the ammo pull.  Gate:
	// per-shot damage > 3.0f (@0x4bc3f4) && owner mech has a gyro.  Direction
	// (0, 0.6, -1.5) (0x3f19999a/0xbfc00000), magnitude = damageAmount/16
	// (@0x4bc3f8).  (The binary's preceding heat add [+0x3d8]->[+0x1c8] gated
	// by HeatModelActive @4ad7d4 is a separate pre-existing gap -- not this hook.)
	if (damageData.damageAmount > 3.0f && owner != 0)
	{
		extern Subsystem *BTMechGyro(void *mech);
		extern void GyroApplyDamageImpulse(Subsystem *, Scalar, Scalar, Scalar, Scalar);
		Subsystem *g = BTMechGyro(owner);				// mech+0x528 via bridge
		if (g != 0)
			GyroApplyDamageImpulse(g, 0.0f, 0.6f, -1.5f,
				damageData.damageAmount * 0.0625f);
	}
```
New accessor in **mech.cpp** (projweap.cpp does not include mech.hpp — databinding rule):
```cpp
// Gyro accessor for TUs without a complete Mech (projweap.cpp recoil @4bc194).
Subsystem *BTMechGyro(void *mech)
{
	return mech != 0 ? ((Mech *)mech)->gyroSubsystem : 0;
}
```
**Do NOT touch `MissileLauncher::FireWeapon`** (mislanch.cpp:217 — its own override, does not chain to @4bc104; binary-verified no gyro kick @4bcc60).

## 3. Argument plumbing (what each site supplies today / gaps)

| Site | type | amount | direction | burstCount | status |
|---|---|---|---|---|---|
| mech.cpp handler | `message->damageData.damageType` | `.damageAmount` | `.damageForce` | `.burstCount` | fields all exist (ENTITY3.h `damageData` @msg+0x2C) |
| crushable crunch | n/a (direct kicks) | fixed 0.4/0.2 | `dmg.damageForce` = Δv from `ProcessCollisionList` (mech4.cpp:3301 → MOVER.cpp:1299) | n/a | zero new plumbing |
| gait jolt/rumble | n/a | fixed 0.4/0.2 | (0,0,±rand) / (0,1,0) | n/a | zero new plumbing |
| recoil | n/a | `damageData.damageAmount * 0.0625f` (mechweap.hpp:326 @0x3A8) | fixed (0,0.6,−1.5) | n/a | zero new plumbing |

**GAP (flag, not a blocker):** our current weapon senders — beam @mech4.cpp:3029-3040 (`kShotDamage`, type Explosive), projectile impact @mech4.cpp:871-878, collision @mech4.cpp:3477 — never set `Damage::damageForce`; the `Damage()` ctor zeroes it (DAMAGE.cpp:38), so every hit takes the fan-out's authentic random-horizontal fallback (@4b29d7). Works and is binary-legal, but for directional fidelity the beam/projectile senders should later fill `dmg.damageForce` with the world flight direction (beam: `impact - muzzle`; projectile: `p.vel`). Also note both senders currently mark beam hits `ExplosiveDamageType` (so `burstCount=1` multiplies benignly); when weapon types are made authentic (Laser/Ballistic), the fan-out picks the matching response row automatically. Second GAP already noted in (d): the FireWeapon heat add is missing (pre-existing, separate).

## 4. Verification checklist (one run, `BT_GYRO_LOG=1`)

1. **Build**: `static_assert` locks compile (0x390 vec, AffineMatrix/ReconMatrix sizes); grep the link log for new unresolved externals (`/FORCE` hides them — `GyroApplyDamage*`, `BTMechGyro` must resolve).
2. **Hit bounce (message path)**: fire at the player mech (or use the enemy shooting back). In `btl4.log` expect, in order, on each hit: the existing BT_GYRO_LOG frame lines showing `eyeForce` spike to nonzero right after the hit → `eyePosition` oscillation over the next ~1s (spring integrator) → decay back to (0,0,0); and `bodyForce`/`bodyOrientation` doing the same (torque + vertical). Add a one-line log in `ApplyDamageResponse` while verifying: `[gyro-dmg] type= amount= burst= dir=(..) t/p/y/v=..` and confirm t/p/y/v are ≤ 1.3 and Explosive scales with burstCount.
3. **Visible jolt**: inside view (cockpit) — the eyepoint/canopy visibly kicks on every laser/missile hit and settles; direction varies (random fallback) run to run.
4. **Collision correctness**: walk into a wall — NO message-path bounce (type 0 early-out; log shows the fan-out entry absent), but walking through a crushable icon logs `[ground] CRUNCH` AND a gyro kick (torque along Δv + 0.2 up).
5. **Recoil**: fire a projectile weapon with damage > 3 — a small forward-down kick per shot (magnitude = damage/16); missiles produce none.
6. **Rumble**: engage the reverse/alternate gait (throttle reverse while cycling) — one Z jolt on the edge, then a 0.4s-period up-shake while `legCycleSpeed > walkStrideLength`; disengage restores `forwardCycleRate = groundCycleRate`.
7. **Regression**: normal combat run un-regressed (no NaN in joints — the eye/mech joint writers already guard); `-net` smoke: replicant-received damage on the master still bounces (binary warns only).
8. **KB**: record the fan-out semantics + the 0x390 vibrationDirection correction and the 0x5c4 re-type in `context/decomp-reference.md` / the gyro section of the task-#56 topic, tiers [T1].

Files touched: `game/reconstructed/gyro.hpp`, `gyro.cpp`, `mech.hpp`, `mech.cpp`, `mech3.cpp` (one line), `mech4.cpp`, `projweap.cpp`. Scratch tooling: `C:\git\bt411\scratchpad\dis_4b2980.py`, `C:\git\bt411\scratchpad\dis_range.py`.