Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ is in `tools/argus_mcp/README.md`.

## Unreleased

**FIXED NAVIGATION TASKS GIVE TUNING AN ABSOLUTE YARDSTICK** (#384). The
`argus-mcp benchmark` runner places a real bot at manifest-declared starts and
measures synthetic-goal completion through the NetQuake puppet, with graph-MD5
pinning, fixed skill and separate train/held-out summaries. The developer-only
QuakeC task controls are inert outside the explicit scratch/impulse protocol.

**HUMANNESS MEASUREMENTS RESPECT LEVEL BOUNDARIES** (#495). Mixed-map
console sessions now contribute only their dominant telemetry segment, matching
the brief parser's established rule. Spawn roles, positions, cells and dwell
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ flowchart TD
- **Static exposure and cover annotations**: The nav compiler runs exact BSP-tree point traces between nodes, ranks each node by how much of the graph it can see, and finds the nearest hidden target reachable by ordinary walk links. Cartograph surfaces the range, the most exposed nodes, and each inspected node's cover target. Runtime treats the result as a hint and verifies it against the live threat.
- **Typed link execution**: Supports walk links, one-way drop links, parabolic jump links (`an_jumpmask`) with per-link approach speeds, rocket-jump links (`an_rjmask`), sprint-jump links (full-run-speed arcs, skill-gated), elevator links (`an_liftmask`), swim-exit and dive links (`an_swimmask`), train rides (`an_trainmask`), and door passages (`an_doormask`).
- **Engine-verdict graph refinement (the mill)**: The lab's puppet client walks accused links in the real engine; refuted walk links whose centre-line void fits the jump envelope are reminted as jump links, unjumpable refusals die, and candidate entries the puppet *proves* are minted from `argus_nav_<map>.proven.json`. Every chronic stall cell fixed this way stays fixed - the engine testifies, navgen re-types, bots inherit.
- **Graph-pinned fixed navigation tasks**: `argus-mcp benchmark benchmarks/dm4_navigation.json` places a real bot at declared starts and measures synthetic-goal completion with fixed skill and no opponents. Manifests pin the graph MD5 and keep train and held-out routes separate, giving tuning an absolute yardstick alongside noisy free-running matches.
- **Door and button handling**: Touch-open doors are walked through (the classname masquerade fires their triggers), button-only doors detour to their button and hold for the slab when the door is near, and shoot-actuated plates are fired at with the bot's own aimed attack.

### 6. Personalities and scoreboard integration
Expand Down Expand Up @@ -178,7 +179,7 @@ argus/
| |-- pak_extract.py # Standalone id1 PAK archive reader / extractor
| |-- mdl_skins.py # Palette-remapped player MDL skin injector
| |-- setup_rig.sh # Automated headless Linux environment setup
| `-- argus_mcp/ # Lab MCP (stdio), `argus-mcp gui`, puppet client, probelinks
| `-- argus_mcp/ # Lab MCP (stdio), GUI, puppet, probelinks, fixed benchmarks
|-- runs/ # Archive of telemetry logs and trajectory plots
| `-- demos/ # Paired .dem recordings (machine-local, not in repo)
|-- backups/ # Dated progs + nav copies (machine-local, not in repo)
Expand Down
37 changes: 37 additions & 0 deletions benchmarks/dm4_navigation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"schema_version": 1,
"name": "dm4_navigation_v1",
"map": "dm4",
"graph_md5": "624a60f0e062f8d5dda4390475077f9a",
"skill": 2,
"tasks": [
{
"name": "pit_green_to_mega",
"start": [689.0, 73.0, -296.0],
"goal": [-47.0, 585.0, -296.0],
"budget_sec": 20.0,
"split": "train"
},
{
"name": "pit_east_to_green",
"start": [81.0, 425.0, -296.0],
"goal": [689.0, 73.0, -296.0],
"budget_sec": 20.0,
"split": "train"
},
{
"name": "lower_west_to_gallery",
"start": [-239.0, -599.0, -72.0],
"goal": [273.0, -1207.0, -24.0],
"budget_sec": 25.0,
"split": "heldout"
},
{
"name": "pit_center_to_east",
"start": [625.0, -23.0, -296.0],
"goal": [81.0, 425.0, -296.0],
"budget_sec": 20.0,
"split": "heldout"
}
]
}
Binary file modified engine/argus/progs.dat
Binary file not shown.
Binary file modified game/argus/progs.dat
Binary file not shown.
119 changes: 118 additions & 1 deletion src/argus.qc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ float argus_nextnames; // next periodic scoreboard rebroadcast
// Route_Finish has to compare the finished flood's errand against the
// bot's current one
.float ar_goalstart;
.entity ar_taskgoal; // developer-only fixed-task marker (#384)
.float ar_taskstart; // game time when the trial began
.float ar_taskdeadline; // hard cleanup ceiling; harness may stop sooner
.float ar_stallrun;
.float ar_mode; // 0 wander, 1 direct, 2 routed
.float ar_nextai;
Expand Down Expand Up @@ -7926,6 +7929,82 @@ void() Argus_CoopThink =
}
};

// External fixed-task control, driven by the lab puppet's impulses 217-219.
// scratch1 3841 places the selected bot at scratch2/3/4; 3842 assigns a
// synthetic goal at those coordinates; 3840 cancels. The two-step protocol
// lets three stock scratch coordinate cvars carry both endpoints without an
// engine extension or file input.
void(entity bot) Argus_TaskClear =
{
local entity marker;

marker = bot.ar_taskgoal;
bot.ar_taskgoal = world;
if (bot.ar_goal == marker)
bot.ar_goal = world;
bot.ar_node = world;
bot.ar_pending = 0;
bot.ar_mode = 0;
if (an_busy && an_bot == bot)
an_busy = 0;
if (marker != world)
remove (marker);
};

void(float slot) Argus_TaskControl =
{
local entity bot, marker;
local float mode;
local vector point;

if (!cvar ("developer"))
return;
mode = cvar ("scratch1");
if (mode != 3840 && mode != 3841 && mode != 3842)
return;

bot = argus_bots;
while (bot != world && bot.ar_slot != slot)
bot = bot.ar_chain;
if (bot == world)
return;

point = cvar ("scratch2") * '1 0 0'
+ cvar ("scratch3") * '0 1 0'
+ cvar ("scratch4") * '0 0 1';

if (mode == 3840)
{
Argus_TaskClear (bot);
return;
}
if (mode == 3841)
{
Argus_TaskClear (bot);
setorigin (bot, point);
bot.velocity = '0 0 0';
bot.ar_lastpos = point;
bot.ar_nextstall = time + 2;
return;
}

Argus_TaskClear (bot);
marker = spawn ();
marker.classname = "argus_task_goal";
marker.solid = SOLID_TRIGGER;
marker.flags = FL_ITEM;
setsize (marker, '-8 -8 -8', '8 8 8');
setorigin (marker, point);
bot.ar_taskgoal = marker;
bot.ar_taskstart = time;
bot.ar_taskdeadline = time + 60;
bot.ar_goal = marker;
bot.ar_goalstart = time + 3600; // ordinary 20 s abandon must not own a task
bot.ar_pending = 1;
bot.ar_mode = 0;
bot.ar_node = world;
};

void() Argus_AI =
{
local vector gp, tp, to, sdir;
Expand All @@ -7935,6 +8014,34 @@ void() Argus_AI =

self.ar_liftwait = 0; // re-earned every frame by the ride branch

// FIXED NAVIGATION TASKS (#384). The external harness places one bot and
// assigns one synthetic goal through Argus_TaskControl. Completion is
// observed here before ordinary arrival handling can clear ar_goal. This
// path is unreachable unless developer mode and scratch1 explicitly arm
// it, so shipped play has no new branch.
if (self.ar_taskgoal != world)
{
if (vlen (self.origin - self.ar_taskgoal.origin) < 32
&& fabs (self.origin_z - self.ar_taskgoal.origin_z) < 56)
{
bprint (PRINT_HIGH, "ARGTASK ");
bprint (PRINT_HIGH, ftos (self.ar_slot));
bprint (PRINT_HIGH, " complete ");
bprint (PRINT_HIGH, ftos (time - self.ar_taskstart));
bprint (PRINT_HIGH, "\n");
Argus_TaskClear (self);
}
else if (time >= self.ar_taskdeadline)
{
bprint (PRINT_HIGH, "ARGTASK ");
bprint (PRINT_HIGH, ftos (self.ar_slot));
bprint (PRINT_HIGH, " timeout ");
bprint (PRINT_HIGH, ftos (time - self.ar_taskstart));
bprint (PRINT_HIGH, "\n");
Argus_TaskClear (self);
}
}

if (coop > 0)
{
Argus_CoopThink ();
Expand Down Expand Up @@ -8164,7 +8271,17 @@ void() Argus_AI =
}

// ---- combat overrides steering when an enemy is engaged ----
fighting = Argus_Combat ();
// A navigation benchmark holds the opponent fixed by taking combat out of
// this bot's steering. The puppet remains connected as the observer, but
// cannot turn a route trial into a duel merely by being visible.
if (self.ar_taskgoal != world)
{
self.ar_enemy = world;
self.button0 = 0;
fighting = FALSE;
}
else
fighting = Argus_Combat ();

// Goal selection happens before combat acquires a fresh target. If this
// life already started the crossing that killed the last one, stop it as
Expand Down
1 change: 1 addition & 0 deletions src/defs.qc
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ void() Argus_ToggleSkillDrift; // ARGUS: opt-in human matchup adjustment
void(entity bot, float won) Argus_SkillResult; // ARGUS: human result
void() Argus_MatchStats; // ARGUS: display match statistics
void(float on) Argus_SetOmicron; // ARGUS: the homage costume
void(float slot) Argus_TaskControl; // ARGUS: developer fixed-task harness
float argus_omicron; // (console "omicron" / impulse 146)
void(float what) Argus_Chat; // ARGUS: bot chat, defined in argus.qc
// and called from powerup_touch (4)
Expand Down
9 changes: 9 additions & 0 deletions src/weapons.qc
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,15 @@ void() ImpulseCommands =
+ cvar ("scratch4") * '0 0 1');
self.velocity = '0 0 0';
}
else if (self.impulse >= 217 && self.impulse <= 219
&& cvar ("developer"))
{
// Developer-only fixed navigation tasks (#384). The puppet chooses
// bot slot 0..2 with impulses 217..219; Argus_TaskControl reads the
// operation and point from scratch1..4. No remote client can arm the
// path without the server console setting developer and scratch1.
Argus_TaskControl (self.impulse - 217);
}
else if (self.impulse >= 220 && self.impulse <= 230)
Argus_PlayerChat (self.impulse);

Expand Down
20 changes: 20 additions & 0 deletions tools/argus_mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,26 @@ engine's lookup and locks onto whatever address answers.
Engine-spawning tests share a lock; a stale engine on 26000 makes
every connect fail, so kill orphans first.

## Fixed navigation benchmark

The benchmark is the low-variance complement to free-running matches. It
places one real Argus bot at a declared nav coordinate, assigns a synthetic
goal, and observes completion through the NetQuake puppet. The other bots are
removed and the task bot ignores the observer, so combat and spawn luck cannot
change the route trial.

```
argus-mcp benchmark benchmarks/dm4_navigation.json [limit] [--no-compile]
```

Suites are JSON manifests containing a map, skill, start/goal coordinates,
budgets, and `train` or `heldout` labels. Each suite pins the MD5 of its nav
JSON: a regenerated graph invalidates the yardstick until its routes are
reviewed deliberately. Reports preserve individual results and summarize
completion rate plus median game time for all, train, and held-out tasks.
Compilation and installation happen by default; `--no-compile` deliberately
measures the currently installed build.

## The mill (probelinks and the verdict files)

Empirical link verification - the killer app the netclient was
Expand Down
Loading