merge lane F: x87 control-word sensitivity - 53-bit and 64-bit identical; 24-bit and round-up each move 2 named leaves
This commit is contained in:
commit
fb23616a8d
11 changed files with 584 additions and 2 deletions
|
|
@ -88,7 +88,7 @@ if(WIN32)
|
|||
shim_colony shim_movement shim_events)
|
||||
target_compile_options(shim_hooks PRIVATE -Wall -Wextra -Werror)
|
||||
|
||||
add_library(binkw32 SHARED src/shim/main.cpp src/shim/binkw32.def)
|
||||
add_library(binkw32 SHARED src/shim/main.cpp src/shim/fpu_force.cpp src/shim/binkw32.def)
|
||||
target_link_libraries(binkw32 PRIVATE minhook sots_addresses shim_trace shim_hooks)
|
||||
target_compile_definitions(binkw32 PRIVATE
|
||||
SHIM_BUILD_ID="${SHIM_BUILD_ID}"
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ hook.Shim::SelfTest::Fill=off
|
|||
trace.path=C:\SOTS\shim.trace.jsonl # default: shim.trace.jsonl beside the DLL; overwritten per run
|
||||
trace.inline_max=256 # regions <= this many bytes are logged as hex, else sha256 + 32-byte head
|
||||
trace.flush=always # always (default) | lazy (flush only on divergence / err / exit)
|
||||
fpu.force=0x007f # force the x87 control word at the turn gate; off (default) = force nothing
|
||||
fpu.sample_ticks=on # on (default): log the control word to shim.log whenever it CHANGES
|
||||
```
|
||||
|
||||
Unusable values are logged to `shim.log` (`config: key=value rejected (...)`) and ignored.
|
||||
|
|
@ -59,6 +61,31 @@ every template hook reports mode `off` (the game runs un-instrumented, never hal
|
|||
The M0 `Mars::Application::Initialize` hook is an asm stub, not a template hook: it is installed
|
||||
whenever `hooks` is not `off`, and it only logs to `shim.log`. See "thiscall" below.
|
||||
|
||||
### `fpu.*` — the x87 control word (`src/shim/fpu_force.cpp`)
|
||||
|
||||
Four more asm-stub hooks, installed alongside the M0 one. They exist to answer "does anything
|
||||
the turn pipeline computes actually *depend* on x87 intermediate precision?", which decides
|
||||
whether a future x64/SSE port has a double-rounding budget to preserve.
|
||||
|
||||
* `fpu.force=<16-bit control word>` writes that word with `fldcw` at `StrategyClient::EndTurn`
|
||||
and `StrategyServer::BeginProcessTurn`, and **nowhere else** -- deliberately a single write per
|
||||
turn, because re-forcing at every hook would guarantee the value is present without proving it
|
||||
ever held. Each site logs observed-before, requested, and read-back-after.
|
||||
* `StrategyServer::ProcessTurn` is hooked **sample-only**: its reading is the independent
|
||||
evidence that the forced word survived into the simulation. The template hooks' own `fpu_cw`
|
||||
fields give ~38 more samples per turn from inside phases 4, 6 and 8.
|
||||
* `fpu.sample_ticks` hooks `DemoApp::OnTick` and logs only when the word *moves*, so `shim.log`
|
||||
carries a timeline rather than a single claim.
|
||||
|
||||
Field layout: bits 0-5 exception masks, bits 8-9 precision control (`00`=24-bit, `10`=53-bit,
|
||||
`11`=64-bit), bits 10-11 rounding control (`00`=nearest, `01`=down, `10`=up, `11`=truncate),
|
||||
bit 12 infinity control (ignored since the 387, so `0x027f` and `0x127f` are the same
|
||||
arithmetic). `Mars::Application::Run` re-arms `0x127f` via `_controlfp` **every frame**, which is
|
||||
why the forcing has to happen inside the turn call chain: a word forced at `EndTurn` is gone by
|
||||
the time `BeginProcessTurn` runs on a later frame.
|
||||
|
||||
Result of the sweep: `notes-repo:findings/subsystems/fpu-precision-sensitivity.md`.
|
||||
|
||||
## Log
|
||||
|
||||
`shim.trace.jsonl`: line 1 is the `meta` record (`build`, `exe_sha256` of the running exe,
|
||||
|
|
|
|||
312
src/shim/fpu_force.cpp
Normal file
312
src/shim/fpu_force.cpp
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
// See fpu_force.h. Register-transparent asm stubs, exactly like main.cpp's Initialize hook:
|
||||
// the prototypes of the turn-gate functions are [unverified], so a C++ detour that *called*
|
||||
// the original would bake in a calling convention. These stubs save everything, call out to C
|
||||
// for the log / the fldcw, and tail-jump to the trampoline, leaving the original's own `ret`
|
||||
// in charge of stack cleanup.
|
||||
|
||||
#include "shim/fpu_force.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "MinHook.h"
|
||||
#include "generated/sots_addresses.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void (*g_line)(const char*) = nullptr;
|
||||
|
||||
void logf(const char* fmt, ...) {
|
||||
if (!g_line) return;
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
std::vsnprintf(buf, sizeof buf, fmt, ap);
|
||||
va_end(ap);
|
||||
g_line(buf);
|
||||
}
|
||||
|
||||
// ---- configuration ---------------------------------------------------------------------------
|
||||
|
||||
bool g_forceEnabled = false;
|
||||
unsigned g_forceValue = 0;
|
||||
bool g_sampleTicks = true;
|
||||
|
||||
// ---- state -----------------------------------------------------------------------------------
|
||||
|
||||
struct ThreadCw {
|
||||
unsigned long tid = 0;
|
||||
std::uint16_t cw = 0;
|
||||
unsigned long ticks = 0;
|
||||
};
|
||||
ThreadCw g_seen[8];
|
||||
unsigned g_seenCount = 0;
|
||||
|
||||
unsigned g_forceEvents = 0; // times we executed fldcw
|
||||
unsigned g_forceFailures = 0; // times the read-back did not match what we asked for
|
||||
unsigned g_changeEvents = 0; // times the tick sampler saw the word move
|
||||
unsigned g_revertEvents = 0; // ... to something other than the value we forced
|
||||
std::uint16_t g_lastSampled = 0;
|
||||
|
||||
// ---- x87 primitives --------------------------------------------------------------------------
|
||||
|
||||
std::uint16_t cw_read() {
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
unsigned short cw = 0;
|
||||
__asm__ __volatile__("fnstcw %0" : "=m"(cw));
|
||||
return cw;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void cw_write(unsigned v) {
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
unsigned short cw = static_cast<unsigned short>(v);
|
||||
__asm__ __volatile__("fldcw %0" : : "m"(cw));
|
||||
#else
|
||||
(void)v;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Human-readable decode, so shim.log states the mode rather than a bare number. The
|
||||
// precision-control field is bits 8-9; the rounding-control field is bits 10-11.
|
||||
const char* pc_name(unsigned cw) {
|
||||
switch ((cw >> 8) & 3) {
|
||||
case 0: return "24bit-single";
|
||||
case 1: return "reserved";
|
||||
case 2: return "53bit-double";
|
||||
default: return "64bit-extended";
|
||||
}
|
||||
}
|
||||
const char* rc_name(unsigned cw) {
|
||||
switch ((cw >> 10) & 3) {
|
||||
case 0: return "nearest";
|
||||
case 1: return "down";
|
||||
case 2: return "up";
|
||||
default: return "truncate";
|
||||
}
|
||||
}
|
||||
|
||||
// Force at a named site and report all three values, so the log proves the write landed
|
||||
// rather than asserting it.
|
||||
void force_at(const char* site, void* self) {
|
||||
const std::uint16_t before = cw_read();
|
||||
if (!g_forceEnabled) {
|
||||
logf("fpu: sample at %s (this=%p): cw=0x%04x %s/%s [no fpu.force configured]", site, self,
|
||||
before, pc_name(before), rc_name(before));
|
||||
return;
|
||||
}
|
||||
cw_write(g_forceValue);
|
||||
const std::uint16_t after = cw_read();
|
||||
++g_forceEvents;
|
||||
const bool ok = (after == static_cast<std::uint16_t>(g_forceValue));
|
||||
if (!ok) ++g_forceFailures;
|
||||
logf("fpu: FORCE at %s (this=%p): observed=0x%04x %s/%s -> requested=0x%04x -> readback=0x%04x "
|
||||
"%s/%s %s",
|
||||
site, self, before, pc_name(before), rc_name(before), g_forceValue, after, pc_name(after),
|
||||
rc_name(after), ok ? "OK" : "*** READBACK MISMATCH ***");
|
||||
}
|
||||
|
||||
// Sample only. Its value at StrategyServer::ProcessTurn is the evidence that a control word
|
||||
// forced at the turn gate survived the gap to the simulation itself, on the same thread.
|
||||
void sample_at(const char* site, void* self) {
|
||||
const std::uint16_t cw = cw_read();
|
||||
logf("fpu: sample at %s (this=%p): cw=0x%04x %s/%s%s", site, self, cw, pc_name(cw), rc_name(cw),
|
||||
(g_forceEnabled && cw != static_cast<std::uint16_t>(g_forceValue))
|
||||
? " *** NOT THE FORCED VALUE ***"
|
||||
: "");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ---- the stubs -------------------------------------------------------------------------------
|
||||
|
||||
extern "C" void* g_origFpuEndTurn;
|
||||
extern "C" void* g_origFpuBeginProcessTurn;
|
||||
extern "C" void* g_origFpuProcessTurn;
|
||||
extern "C" void* g_origFpuOnTick;
|
||||
void* g_origFpuEndTurn = nullptr;
|
||||
void* g_origFpuBeginProcessTurn = nullptr;
|
||||
void* g_origFpuProcessTurn = nullptr;
|
||||
void* g_origFpuOnTick = nullptr;
|
||||
|
||||
extern "C" void FpuEndTurnDetour();
|
||||
extern "C" void FpuBeginProcessTurnDetour();
|
||||
extern "C" void FpuProcessTurnDetour();
|
||||
extern "C" void FpuOnTickDetour();
|
||||
|
||||
extern "C" void ShimFpuOnEndTurn(void* self) { force_at("StrategyClient::EndTurn", self); }
|
||||
extern "C" void ShimFpuOnBeginProcessTurn(void* self) {
|
||||
force_at("StrategyServer::BeginProcessTurn", self);
|
||||
}
|
||||
extern "C" void ShimFpuOnProcessTurn(void* self) { sample_at("StrategyServer::ProcessTurn", self); }
|
||||
|
||||
// Per-frame sampler. Logs only when the word moves (or on the first tick seen on a thread), so
|
||||
// shim.log ends up holding a complete timeline of the control word for the whole session
|
||||
// instead of one claim made at one instant.
|
||||
extern "C" void ShimFpuOnTick(void* self) {
|
||||
if (!g_sampleTicks) return;
|
||||
const std::uint16_t cw = cw_read();
|
||||
g_lastSampled = cw;
|
||||
const unsigned long tid = GetCurrentThreadId();
|
||||
for (unsigned i = 0; i < g_seenCount; ++i) {
|
||||
if (g_seen[i].tid != tid) continue;
|
||||
++g_seen[i].ticks;
|
||||
if (g_seen[i].cw == cw) return;
|
||||
const std::uint16_t prev = g_seen[i].cw;
|
||||
g_seen[i].cw = cw;
|
||||
++g_changeEvents;
|
||||
const bool away = g_forceEnabled && cw != static_cast<std::uint16_t>(g_forceValue);
|
||||
if (away) ++g_revertEvents;
|
||||
logf("fpu: TICK CHANGE at OnTick (this=%p) after %lu ticks: 0x%04x -> 0x%04x %s/%s%s", self,
|
||||
g_seen[i].ticks, prev, cw, pc_name(cw), rc_name(cw),
|
||||
away ? " *** REVERTED AWAY FROM THE FORCED VALUE ***" : "");
|
||||
return;
|
||||
}
|
||||
if (g_seenCount < (sizeof g_seen / sizeof g_seen[0])) {
|
||||
g_seen[g_seenCount].tid = tid;
|
||||
g_seen[g_seenCount].cw = cw;
|
||||
g_seen[g_seenCount].ticks = 1;
|
||||
++g_seenCount;
|
||||
logf("fpu: TICK BASELINE at OnTick (this=%p): cw=0x%04x %s/%s", self, cw, pc_name(cw),
|
||||
rc_name(cw));
|
||||
}
|
||||
}
|
||||
|
||||
asm(R"(
|
||||
.text
|
||||
.globl _FpuEndTurnDetour
|
||||
_FpuEndTurnDetour:
|
||||
pushfl
|
||||
pushal
|
||||
pushl %ecx
|
||||
call _ShimFpuOnEndTurn
|
||||
addl $4, %esp
|
||||
popal
|
||||
popfl
|
||||
jmp *_g_origFpuEndTurn
|
||||
|
||||
.globl _FpuBeginProcessTurnDetour
|
||||
_FpuBeginProcessTurnDetour:
|
||||
pushfl
|
||||
pushal
|
||||
pushl %ecx
|
||||
call _ShimFpuOnBeginProcessTurn
|
||||
addl $4, %esp
|
||||
popal
|
||||
popfl
|
||||
jmp *_g_origFpuBeginProcessTurn
|
||||
|
||||
.globl _FpuProcessTurnDetour
|
||||
_FpuProcessTurnDetour:
|
||||
pushfl
|
||||
pushal
|
||||
pushl %ecx
|
||||
call _ShimFpuOnProcessTurn
|
||||
addl $4, %esp
|
||||
popal
|
||||
popfl
|
||||
jmp *_g_origFpuProcessTurn
|
||||
|
||||
.globl _FpuOnTickDetour
|
||||
_FpuOnTickDetour:
|
||||
pushfl
|
||||
pushal
|
||||
pushl %ecx
|
||||
call _ShimFpuOnTick
|
||||
addl $4, %esp
|
||||
popal
|
||||
popfl
|
||||
jmp *_g_origFpuOnTick
|
||||
)");
|
||||
|
||||
namespace shim::fpu {
|
||||
|
||||
std::uint16_t read_cw() { return cw_read(); }
|
||||
|
||||
bool apply_config(const char* key, const char* value, std::string* err) {
|
||||
if (std::strcmp(key, "fpu.force") == 0) {
|
||||
if (value[0] == '\0' || std::strcmp(value, "off") == 0 || std::strcmp(value, "none") == 0) {
|
||||
g_forceEnabled = false;
|
||||
return true;
|
||||
}
|
||||
char* end = nullptr;
|
||||
unsigned long v = std::strtoul(value, &end, 0);
|
||||
if (end == value || *end != '\0' || v > 0xffff) {
|
||||
if (err) *err = "expected off or a 16-bit control word (e.g. 0x127f)";
|
||||
return true;
|
||||
}
|
||||
g_forceValue = static_cast<unsigned>(v);
|
||||
g_forceEnabled = true;
|
||||
return true;
|
||||
}
|
||||
if (std::strcmp(key, "fpu.sample_ticks") == 0) {
|
||||
if (std::strcmp(value, "on") == 0) {
|
||||
g_sampleTicks = true;
|
||||
return true;
|
||||
}
|
||||
if (std::strcmp(value, "off") == 0) {
|
||||
g_sampleTicks = false;
|
||||
return true;
|
||||
}
|
||||
if (err) *err = "expected on or off";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string summary() {
|
||||
char buf[320];
|
||||
std::snprintf(buf, sizeof buf,
|
||||
"fpu: SUMMARY force=%s value=0x%04x fldcw_events=%u readback_failures=%u "
|
||||
"tick_changes=%u tick_reverts=%u last_sampled=0x%04x",
|
||||
g_forceEnabled ? "on" : "off", g_forceValue, g_forceEvents, g_forceFailures,
|
||||
g_changeEvents, g_revertEvents, g_lastSampled);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void install(std::uintptr_t exeBase, void (*log)(const char*)) {
|
||||
g_line = log;
|
||||
const std::uint16_t entry = cw_read();
|
||||
logf("fpu: module init, entry cw=0x%04x %s/%s; force=%s value=0x%04x sample_ticks=%s", entry,
|
||||
pc_name(entry), rc_name(entry), g_forceEnabled ? "on" : "off", g_forceValue,
|
||||
g_sampleTicks ? "on" : "off");
|
||||
|
||||
struct Site {
|
||||
const char* name;
|
||||
std::uint32_t rva;
|
||||
void* detour;
|
||||
void** orig;
|
||||
bool install;
|
||||
};
|
||||
const Site sites[] = {
|
||||
{"StrategyClient::EndTurn", sots::addr::StrategyClient_EndTurn,
|
||||
reinterpret_cast<void*>(&FpuEndTurnDetour), &g_origFpuEndTurn, true},
|
||||
{"StrategyServer::BeginProcessTurn", sots::addr::StrategyServer_BeginProcessTurn,
|
||||
reinterpret_cast<void*>(&FpuBeginProcessTurnDetour), &g_origFpuBeginProcessTurn, true},
|
||||
{"StrategyServer::ProcessTurn", sots::addr::StrategyServer_ProcessTurn,
|
||||
reinterpret_cast<void*>(&FpuProcessTurnDetour), &g_origFpuProcessTurn, true},
|
||||
{"DemoApp::OnTick", sots::addr::DemoApp_OnTick, reinterpret_cast<void*>(&FpuOnTickDetour),
|
||||
&g_origFpuOnTick, g_sampleTicks},
|
||||
};
|
||||
for (const Site& s : sites) {
|
||||
if (!s.install) {
|
||||
logf("fpu: %s rva=0x%08x not installed (sampler off)", s.name, s.rva);
|
||||
continue;
|
||||
}
|
||||
void* target = reinterpret_cast<void*>(exeBase + s.rva);
|
||||
MH_STATUS st = MH_CreateHook(target, s.detour, s.orig);
|
||||
logf("fpu: %s rva=0x%08x -> va=%p MH_CreateHook -> %s (trampoline=%p)", s.name, s.rva,
|
||||
target, MH_StatusToString(st), *s.orig);
|
||||
if (st != MH_OK) continue;
|
||||
st = MH_EnableHook(target);
|
||||
logf("fpu: %s MH_EnableHook -> %s", s.name, MH_StatusToString(st));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace shim::fpu
|
||||
48
src/shim/fpu_force.h
Normal file
48
src/shim/fpu_force.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// x87 control-word forcing + timeline sampling (lane F: the precision-sensitivity experiment).
|
||||
//
|
||||
// The question this exists to answer: does any value the turn pipeline produces actually
|
||||
// *depend* on x87 intermediate precision? Every save on the lab host was made with the game's
|
||||
// own control word (0x127f = 53-bit significand, round-to-nearest). A future x64/SSE port has
|
||||
// no 80-bit intermediates at all, so "bit-exact" is only a real constraint if changing the
|
||||
// precision-control field changes the resulting state.
|
||||
//
|
||||
// Method: force the control word once, at the entry to the turn gate, then let the whole
|
||||
// pipeline run under it and checksum the autosave. Forcing is deliberately a *single* write at
|
||||
// the top of the turn -- re-forcing at every hook would guarantee the value is present without
|
||||
// proving it ever held, which is exactly the false-negative this experiment must avoid.
|
||||
//
|
||||
// Verification is separate from forcing:
|
||||
// * every force/sample site logs observed-before, requested, and read-back-after;
|
||||
// * the sampler on DemoApp::OnTick logs the control word on every *change*, per thread, so
|
||||
// shim.log carries a complete timeline of the value for the session rather than a claim;
|
||||
// * the existing hooks (research, colony turn, fleet movement, compute budget) already emit
|
||||
// `fpu_cw` per call, giving independent samples from inside phases 4, 6 and 8 of the turn.
|
||||
//
|
||||
// shim.cfg keys owned here:
|
||||
// fpu.force = 0x027f | 0x127f | 0x137f | <hex> control word to force at the turn gate
|
||||
// (absent or 0 = force nothing, sample only)
|
||||
// fpu.sample_ticks = on | off per-tick change sampler (default on)
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace shim::fpu {
|
||||
|
||||
// Handles the `fpu.*` shim.cfg keys. Returns true when `key` belongs to this module; sets
|
||||
// *err (and leaves the setting unchanged) when the key is ours but the value is unusable.
|
||||
bool apply_config(const char* key, const char* value, std::string* err);
|
||||
|
||||
// Read the x87 control word of the calling thread.
|
||||
std::uint16_t read_cw();
|
||||
|
||||
// One line for shim.log at shutdown: how many times the word was forced, whether any read-back
|
||||
// disagreed, and whether the per-tick sampler ever saw it move away again.
|
||||
std::string summary();
|
||||
|
||||
// Installs the force/sample hooks. `log` receives one preformatted line at a time.
|
||||
// Safe to call with no `fpu.force` configured: the sampler still runs, which is what makes a
|
||||
// stock 0x127f run comparable to a forced one.
|
||||
void install(std::uintptr_t exeBase, void (*log)(const char*));
|
||||
|
||||
} // namespace shim::fpu
|
||||
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "MinHook.h"
|
||||
#include "generated/sots_addresses.h"
|
||||
#include "shim/fpu_force.h"
|
||||
#include "shim/hooks/dictionaries.h"
|
||||
#include "shim/hooks/colony_turn.h"
|
||||
#include "shim/hooks/compute_budget.h"
|
||||
|
|
@ -79,7 +80,10 @@ Config ReadConfig() {
|
|||
char* val = eq + 1;
|
||||
val[std::strcspn(val, "\r\n")] = '\0';
|
||||
std::string err;
|
||||
if (cfg.trace.apply(p, val, &err)) {
|
||||
if (shim::fpu::apply_config(p, val, &err)) {
|
||||
if (!err.empty()) Log("config: %s=%s rejected (%s)", p, val, err.c_str());
|
||||
else Log("config: %s=%s", p, val);
|
||||
} else if (cfg.trace.apply(p, val, &err)) {
|
||||
if (!err.empty()) Log("config: %s=%s rejected (%s)", p, val, err.c_str());
|
||||
else Log("config: %s=%s", p, val);
|
||||
} else {
|
||||
|
|
@ -207,6 +211,10 @@ void InstallHooks(shim::trace::Tracer& tracer) {
|
|||
shim::hooks::init_fleet_movement(exeBase, &ShimLogLine);
|
||||
InstallTemplateHook<shim::hooks::StrategyServerMoveFleetHook>(tracer, exeBase, sots::addr::StrategyServer_MoveFleet);
|
||||
InstallTemplateHook<shim::hooks::StrategyServerProcessFleetMovementHook>(tracer, exeBase, sots::addr::StrategyServer_ProcessFleetMovement);
|
||||
|
||||
// Lane F: x87 control-word forcing at the turn gate + the per-tick change sampler.
|
||||
// Installed last so it is nowhere near the template hooks it is meant to measure.
|
||||
shim::fpu::install(exeBase, &ShimLogLine);
|
||||
}
|
||||
|
||||
// ---- lifecycle -----------------------------------------------------------------------------
|
||||
|
|
@ -282,6 +290,7 @@ void Shim_Init(HMODULE self) {
|
|||
}
|
||||
|
||||
void Shim_Shutdown() {
|
||||
Log("%s", shim::fpu::summary().c_str());
|
||||
shim::trace::Tracer& tracer = shim::trace::Tracer::instance();
|
||||
if (tracer.is_open()) {
|
||||
Log("shutdown: trace records=%lu", static_cast<unsigned long>(tracer.records_written()));
|
||||
|
|
|
|||
31
src/shim/shim.cfg.fpu007f
Normal file
31
src/shim/shim.cfg.fpu007f
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# 0x007f = 24-bit significand (SINGLE precision), round-to-nearest. The genuine single-precision setting the brief meant by 0x027f, and the run most likely to move a result: it is the strongest available positive control on the whole apparatus.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=0x007f
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
31
src/shim/shim.cfg.fpu027f
Normal file
31
src/shim/shim.cfg.fpu027f
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# 0x027f = 53-bit significand, round-to-nearest. The MSVC CRT default. Numerically identical to the game's own 0x127f: they differ only in bit 12 (infinity control), which the 387 and later ignore. Named in the lane brief as "24-bit"; it is not.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=0x027f
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
31
src/shim/shim.cfg.fpu127f
Normal file
31
src/shim/shim.cfg.fpu127f
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# 0x127f = 53-bit significand, round-to-nearest. THE CONTROL: exactly what the game sets itself, so this run must reproduce the stock oracle hashes.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=0x127f
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
31
src/shim/shim.cfg.fpu137f
Normal file
31
src/shim/shim.cfg.fpu137f
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# 0x137f = 64-bit significand (x87 extended), round-to-nearest. Named in the lane brief as "53-bit, different rounding"; it is in fact the precision axis, and the one an x64/SSE port cannot reproduce.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=0x137f
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
31
src/shim/shim.cfg.fpu1a7f
Normal file
31
src/shim/shim.cfg.fpu1a7f
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# 0x1a7f = 53-bit significand, round-toward-+infinity. The genuine rounding-mode change the brief meant by 0x137f.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=0x1a7f
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
31
src/shim/shim.cfg.fpuoff
Normal file
31
src/shim/shim.cfg.fpuoff
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Lane F, x87 precision-sensitivity experiment (verify/results/fpu-cw in the notes repo).
|
||||
# STOCK: nothing forced. Oracle re-confirmation and the game's own control-word timeline.
|
||||
#
|
||||
# Every shim.cfg.fpu* file is identical except the fpu.force line: the hook set, the trace path
|
||||
# and the flush policy are held fixed so the control word is the only variable across runs.
|
||||
#
|
||||
# x87 control-word fields: bits 0-5 exception masks, bits 8-9 precision control
|
||||
# (00 = 24-bit / 10 = 53-bit / 11 = 64-bit significand), bits 10-11 rounding control
|
||||
# (00 nearest / 01 down / 10 up / 11 truncate), bit 12 infinity control (ignored since the 387).
|
||||
hooks=trace
|
||||
hook.Shim::SelfTest::Fill=off
|
||||
hook.Mars::GlobalConsts::LoadFile=off
|
||||
hook.Game::WeaponDictionary::Init=off
|
||||
hook.Game::SectionDictionary::SectionDictionary=off
|
||||
hook.Game::StrategyServer::ProcessFleetMovement=off
|
||||
hook.Game::TechTree::ProcessResearch=trace
|
||||
hook.Game::ServerPlayer::ComputeBudget=trace
|
||||
hook.Game::ServerPlayer::OnTechResearched=trace
|
||||
hook.Game::ServerSystem::ProcessTurn=trace
|
||||
hook.Game::StrategyServer::MoveFleet=trace
|
||||
trace.path=C:\SOTS\shim.trace.jsonl
|
||||
trace.inline_max=256
|
||||
trace.flush=always
|
||||
|
||||
# Forced once at the turn gate (StrategyClient::EndTurn and StrategyServer::BeginProcessTurn),
|
||||
# never re-forced inside the pipeline -- re-forcing would guarantee the value is present without
|
||||
# proving it ever held.
|
||||
fpu.force=off
|
||||
# Per-tick sampler: logs to shim.log whenever the word MOVES, so the log carries a timeline of
|
||||
# the value rather than a single claim made at a single instant.
|
||||
fpu.sample_ticks=on
|
||||
Loading…
Add table
Reference in a new issue