Files
BT411/engine/MUNGA/SIMULATE.h
T
Joe DiPrimaandClaude Fable 5 e1c15eb806 #82 peers see a limping mech SKATING: the mech erased its own gimp cell every frame
The binary's one mech+0x40 IS Simulation::simulationState, which rides EVERY
update record header -- so in 1995 a peer's replicant learned the gimp level on
every packet and its gait limped with no gimp-specific replication anywhere.
The port's Mech::PerformAndWatch wrote SetMovementMode(1) every frame ("ground,
non-death, non-airborne"), which erased the level once it was mirrored in for
the warning voice (#78): the wire carried 1, bystanders walked while sliding at
limp speed, and the voice sequence restarted on every damage event (1->4 edge
per tick) instead of announcing once.

- mech4: that per-frame write now writes the AUTHORITATIVE level
  (gimped ? 3/4 : 1) via a new alarm-only bridge BTMechGimpAlarmLevel -- which
  deliberately never consults the cell it feeds, so a respawn-cleared alarm
  cannot re-latch stale gimp out of it.
- BTMechGimpLevel: falls back to the replicated cell, with ONE-CELL precedence
  (a fall/death/limbo state wins, so the normal drivers and their death latch
  run -- what the binary's single cell enforced structurally).
- Reverted the #78 record guard: on a replicant the master's records are
  authoritative, so pinning 3/4 against them would keep a peer limping through
  a respawn.  Fixed at the writer instead.
- [simstomp] trap now scoped to the watched mech with a module-relative return
  address (symcrash-able) -- that is what named the writer.
- Also learned + recorded: zone damage levels replicate only when the EXPLOSION
  TABLE's tier is crossed (peer measured at 0.428 vs master 0.857), so peer-side
  damage state must never be inferred from them.

Verified two-node (scratchpad/night6/mp_skate.sh): the observer's replicant gets
sim=4 and its gait runs 23 -> 25 (wgr entry -> ggl limp cycle).  Field clue that
cracked it: "after respawning a peer DID see the limp" (epilectrik/SAURON).
KB: locomotion.md + gotcha #25.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 22:29:03 -05:00

409 lines
9.8 KiB
C++

#pragma once
#include "state.h"
#include "receiver.h"
#include <intrin.h> // _ReturnAddress (the #78 simstomp diag)
#include "time.h"
#include "resource.h"
class Simulation__SharedData;
class Simulation__IndexData;
struct Simulation__IndexEntry;
class Simulation__AttributeIndexSet;
class MemoryStream;
//##########################################################################
//################# Simulation::UpdateRecord #########################
//##########################################################################
struct Simulation__UpdateRecord
{
public:
size_t recordLength;
Word subsystemID;
Word recordID;
Time timeStamp;
Enumeration simulationState;
};
//##########################################################################
//####################### Simulation #################################
//##########################################################################
class Simulation:
public Receiver
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Shared Data Support
//
public:
typedef Simulation__SharedData SharedData;
SharedData*
GetSharedData();
static Derivation *GetClassDerivations();
static SharedData DefaultData;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction Support
//
protected:
Simulation(
ClassID class_ID,
SharedData &shared_data
);
public:
~Simulation();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Attribute Support
//
public:
typedef Enumeration AttributeID;
typedef Simulation__IndexData IndexData;
typedef Simulation__IndexEntry IndexEntry;
typedef Simulation__AttributeIndexSet AttributeIndexSet;
typedef int Simulation::*AttributePointer;
enum {
AnyAttributeID = 0,
SimulationStateAttributeID,
NextAttributeID
};
static const AttributePointer NullAttribute;
void*
GetAttributePointer(AttributeID attribute);
void*
GetAttributePointer(const char* attribute_name);
private:
static const IndexEntry AttributePointers[];
protected:
//static AttributeIndexSet AttributeIndex
static AttributeIndexSet& GetAttributeIndex();
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Simulation Support
//
public:
typedef void
(Simulation::*Performance)(Scalar time_slice);
typedef void
(Simulation::*Encore)();
typedef Simulation__UpdateRecord UpdateRecord;
void
SetPerformance(Performance performance)
{Check(this); activePerformance = performance;}
void
Perform(Scalar time_slice)
{
Check(this);
(this->*activePerformance)(time_slice);
}
virtual void
PerformAndWatch(
const Time& till,
MemoryStream *update_stream
);
void
DoNothingOnce(Scalar time_slice);
void
DoNothing(Scalar time_slice);
void
SetLastPerformance(const Time& when)
{Check(this); Check(&when); lastPerformance = when;}
void
RequestEncore(Encore encore);
virtual void
ReadUpdateRecord(UpdateRecord *message);
virtual void
WriteUpdateRecord(
UpdateRecord *message,
int update_model
);
void
WriteSimulationUpdate(MemoryStream *update_stream);
enum {
DefaultUpdateModelBit=0,
NextUpdateModelBit
};
enum {
DefaultUpdateModelFlag = 1<<DefaultUpdateModelBit
};
void
ForceUpdate(Word model=DefaultUpdateModelFlag)
{Check(this); updateModel |= model;}
protected:
Time
lastPerformance;
Time
lastUpdate;
Word
updateModel;
Performance
activePerformance;
//##########################################################################
// Flag Support
//
public:
enum {
DelayWatchersBit,
DontExecuteBit,
NextBit
};
enum {
DelayWatchersFlag = 1<<DelayWatchersBit,
DontExecuteFlag = 1<<DontExecuteBit
};
LWord simulationFlags;
void
SetWatcherDelay()
{Check(this); simulationFlags |= DelayWatchersFlag;}
void
ClearWatcherDelay()
{Check(this); simulationFlags &= ~DelayWatchersFlag;}
Logical
AreWatchersDelayed()
{Check(this); return (simulationFlags & DelayWatchersFlag) != 0;}
void
NeverExecute()
{Check(this); simulationFlags |= DontExecuteFlag;}
void
ExecuteOnUpdate()
{Check(this); simulationFlags |= DontExecuteFlag;}
void
AlwaysExecute()
{Check(this); simulationFlags &= ~DontExecuteFlag;}
Logical
IsReplicantExecutable()
{
Check(this);
return
(simulationFlags&DontExecuteFlag) == 0
|| lastUpdate >= lastPerformance;
}
Logical
IsNonReplicantExecutable()
{Check(this); return (simulationFlags&DontExecuteFlag) == 0;}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// State support
//
public:
enum {
DefaultState = 0,
StateCount
};
unsigned
GetSimulationState()
{Check(this); return simulationState.GetState();}
unsigned
GetOldSimulationState()
{Check(this); return simulationState.GetOldState();}
void
SetSimulationState(unsigned new_state)
{Check(this);
// #78 DIAG: trap the gimp-cell downgrade (3/4 -> <=1) with the
// caller's return address (symbolize via tools/symcrash.py).
// #78/#82 DIAG: who resets the GIMP cell? Scoped to the one mech
// mechdmg just gimped (g_btGimpWatchMech) -- subsystems are
// Simulations too and their 3->0 churn drowned the earlier trap.
// ra is printed MODULE-RELATIVE (btl4+0xNNNN) for symcrash.py.
{
extern void *g_btGimpWatchMech;
extern void BTGimpStompTrap(void *sim, unsigned cur,
unsigned nw, void *ra);
unsigned _cur = simulationState.GetState();
if ((void*)this == g_btGimpWatchMech
&& (_cur == 3 || _cur == 4) && new_state != _cur)
BTGimpStompTrap((void*)this, _cur, new_state,
_ReturnAddress());
}
simulationState.SetState(new_state);}
StateIndicator
simulationState;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Watcher Support
//
public:
void
AddAudioWatcher(Component *watcher)
{Check(&audioWatcherSocket);audioWatcherSocket.Add(watcher);}
void
AddVideoWatcher(Component *watcher)
{Check(&videoWatcherSocket);videoWatcherSocket.Add(watcher);}
void
AddGaugeWatcher(Component *watcher)
{Check(&gaugeWatcherSocket);gaugeWatcherSocket.Add(watcher);}
void
AddEffectWatcher(Component *watcher)
{Check(&effectWatcherSocket); effectWatcherSocket.Add(watcher);}
void
ExecuteWatchers();
int // DEBUG (BT_AUDIO_LOG): how many audio watchers are registered on this sim
DebugAudioWatcherCount();
private:
SChainOf<Component*>
audioWatcherSocket;
SChainOf<Component*>
videoWatcherSocket;
SChainOf<Component*>
gaugeWatcherSocket;
SChainOf<Component*>
effectWatcherSocket;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Support
//
public:
Logical
TestInstance() const;
static Logical
TestClass();
};
//##########################################################################
//################### Simulation::IndexEntry #########################
//##########################################################################
struct Simulation__IndexEntry
{
Enumeration
entryID;
const char *
entryName;
Simulation::AttributePointer
entryAddress;
};
#define ATTRIBUTE_ENTRY(class,name,attribute)\
{\
class::name##AttributeID,\
#name,\
(Simulation::AttributePointer) &class::attribute\
}
//##########################################################################
//################# Simulation::AttributeIndexSet ####################
//##########################################################################
class Simulation__AttributeIndexSet:
public Receiver::InheritanceSet
{
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Construction and Destruction
//
public:
Simulation__AttributeIndexSet(
Simulation::AttributeID count,
const Simulation::IndexEntry index_table[],
const Simulation::AttributeIndexSet &inheritance
)
{Build(count, index_table, &inheritance);}
Simulation__AttributeIndexSet(
Simulation::AttributeID count,
const Simulation::IndexEntry index_table[]
)
{Build(count, index_table, NULL);}
Simulation__AttributeIndexSet()
{attributeIndex = NULL; entryCount = 0;}
~Simulation__AttributeIndexSet();
protected:
void
Build(
Simulation::AttributeID count,
const Simulation::IndexEntry index_table[],
const Simulation::AttributeIndexSet *inheritance
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// AttributeIndexSet Functionality
//
protected:
Simulation::IndexEntry
*attributeIndex;
public:
Simulation::AttributePointer
Find(Simulation::AttributeID attribute) const
{
Check(this);
Verify(attribute > 0);
if (attribute<=entryCount)
return attributeIndex[attribute-1].entryAddress;
else
return Simulation::NullAttribute;
}
Simulation::AttributePointer
Find(const char* attribute_name) const;
const Simulation::IndexEntry*
FindEntry(const char* attribute_name) const;
static const Simulation::AttributeIndexSet
NullSet;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Support
//
public:
static Logical
TestClass();
};
//##########################################################################
//################### Simulation::SharedData #########################
//##########################################################################
class Simulation__SharedData:
public Receiver::SharedData
{
public:
Simulation__SharedData(
Derivation* derivation,
Receiver::MessageHandlerSet &message_handlers,
Simulation::AttributeIndexSet &attribute_index,
int state_count
):
Receiver::SharedData(derivation, message_handlers),
activeAttributeIndex(&attribute_index),
stateCount(state_count)
{}
Simulation::AttributeIndexSet* activeAttributeIndex;
int stateCount;
};
inline Simulation::SharedData*
Simulation::GetSharedData()
{return Cast_Object(SharedData*,sharedData);}