Lands tasks 1.0-1.8 of multiplayer-todo.md: the pure-function test runner,
net_codec (wire format quantizers/pack-unpack), NetworkManager (ENet
transport, manual polling, min-RTT clock sync), MatchNet (handshake,
protocol/tick-rate gating, roster with team+ready state), lobby.tscn (team
columns, switch team, ready toggle), server_boot.tscn (headless dedicated
server with structured logging and an overrun watchdog), and main_menu.gd's
Host/Join-by-IP UI (connecting overlay, cancel, bounded failure path).
Followed by an adversarial review (Opus subagent) that found and fixed two
real bugs - an unvalidated player_name broadcast that let one client's
oversized name head-of-line-block the reliable channel for everyone, and a
server-side roster leak across a host/re-host cycle - plus three gaps in
the test suite itself where a claim of "verified" wasn't actually backed
by what the test checked. All five two-process smoke tests plus the
pure-function suite are green with the strengthened assertions in place.
match.tscn had picked up team_size=3 from an earlier diagnostic dry run,
which would have made every normal Match spawn 3v3 instead of 1v1 -
reverted to the scene's intended default.
multiplayer-todo.md: task 0.15b's real blocker turned out to be measuring
on a Mac (Apple Silicon's tile-based GPU architecture gave a misleading,
undifferentiated cost profile). Re-ran the same 6-ship-match profiling
harness on reference hardware (RTX 3090) via a real GPU-bound X session -
results in §5.5.2 show the game comfortably clears 500+fps with every
effect on, and SDFGI/SSIL dominate the (now tiny) effects budget as
originally expected. This closes 0.28 (physics threading) as unnecessary
- there's no frame-time variance problem on reference hardware to fix -
and reframes 0.26 (bake GI) as a real but smaller win than assumed, worth
revisiting on lower-end hardware. Also corrected two stale/inaccurate
task rows (0.13, 0.17) found while reconciling the doc against what
actually landed.
Lands the non-networked Phase 0 tasks from multiplayer-todo.md (ship/camera/
arena refactors, sim constants, background FPS handling) plus a first pass
at exposing graphics/performance settings (presets, resolution scaling,
vsync, FPS cap, perf overlay) and a GPU profiling harness for the
real-hardware follow-up in task 0.15b.
Stage 5 blocked all three attempts on productive_air_touch_fraction
stuck exactly at 0.0 across a continuous 180M-step lineage, while
goal_rate/upright_fraction/forward_motion_fraction kept improving on
the same budget. forward_velocity_to_ball_weight (the term that solved
Stage 4's ground pursuit) is hard-gated below GROUND_HANDLING_HEIGHT
and does nothing in the air, so Stage 5's air_intercept_chance had no
matching aerial incentive to learn from. air_approach_weight adds the
airborne mirror (nose-first 3D closing speed, no uprightness
multiplier) and folds into HANDLING_REWARD_FLAGS so Stage 6 inherits
it too. Deleted the three blocked attempts and reset state to resume
Stage 5 from the Stage-4 checkpoint with the new term.
20260816-2126-gen5-s4-handling-retry2 exhausted its three attempts and
missed only the 0.80 training goal-rate floor, at 0.7731. Every
evaluation gate passed: 65-22-13 versus promoted/easy.json, 87% non-draw
against an 80% floor, 12.6% physical-side imbalance against a 20%
ceiling, and both handling telemetry floors clear. The round improved the
goal rate monotonically across attempts (0.537 -> 0.683 -> 0.773) and the
checkpoint plays well by hand, so close Stage 4 by human override.
Promote it to Game/bots/promoted/medium.json. Medium and Hard both point
at the new policy: Hard stays a label-only duplicate until a stronger one
earns hard.json, which keeps the tiers monotonic rather than leaving Hard
weaker than Medium.
generation5_state.json flips that log entry to "pass" with a
decision_override block preserving the original verdict and reasoning,
and advances to Stage 5 attempt 1. This is what passing_entry() needs to
resolve Stage 5's resume checkpoint and evaluation reference, and what
league_pool() will need at Stage 6; --skip-to-next-stage would advance
the stage without marking anything as passing and die immediately.
generation5.sh now pulls before launching. Each stage ends in
commit_progress()'s push, which fails and kills the run hours in if the
box is behind origin.
Upstream's update added telemetry that is on by default. Sets the three
opt-out env vars on the blender server in .mcp.json, which also suppresses
the consent prompt it would otherwise raise through the MCP client.
Documents the addon-side 'Allow Telemetry' preference as a separate switch:
it defaults to on, covers prompts, code snippets, screenshots and trajectory
data, and lives inside Blender where these env vars cannot reach it.
Upstream replaced the manual GUI addon install with a version-aware
`blender-mcp install-addon` CLI, so the agent guide now specifies a
uv sync + install-addon preflight before the first blender-mcp tool
call in a session, and narrows the manual steps to what genuinely
cannot be scripted: enabling the addon, starting the socket server,
and restarting Blender after the addon file is rewritten.
Also fixes the post-clone setup line, which gave `git submodule add`
(the command for vendoring it in the first place) instead of
`git submodule update --init --recursive`, and notes the telemetry
opt-out env vars that came with the update.
Moves the vendored blender-mcp from 3ab89251 to fbf9777. The checked-out
main had diverged from ahujasid/blender-mcp (10 ahead, 5 behind) after an
upstream history rewrite; the local-only commits were third-party README
work that no longer exists upstream.
Brings in addon bundling, a consent prompt, expanded telemetry, a Windows
hang fix, and a dependency change (mcp[cli] extra dropped) — so uv sync
and a manual addon reinstall in Blender are needed.
Six rounds of reward shaping (~700M steps) failed to produce upright ground
driving. A critical review of the simulation rather than the reward found
why:
1. The hull was a 1x1x4 box with inertia (1,1,1) and no restoring torque
anywhere, so belly-down and rolled-90 were geometrically identical
resting states. "Upright" was not a physically distinguished state at
all - the reward was paying for a property the simulation did not have.
2. ~65% of episodes spawned ships via _random_position, which samples Y
uniformly over the full 18m volume (mean ~8.7m). The measured
airborne_fraction ~0.44 was largely that spawn distribution, and every
ground-handling term fades out above 3m, so the shaping being tuned
barely ever applied.
3. air_drill_chance 0.20 spawned deliberately unreachable-without-climbing
states in the stage meant to teach ground driving, and its own
air_touch_fraction (0.0002) shows the drills were never solved.
Fixes land in the physics and the task distribution, not the reward:
- ship.tscn: hull 1x1x4 -> 1.6x0.6x4 so it has one stable resting face;
inertia (1,1,1) -> (7,1,7), physically correct for the hull, making
tumbling reluctant while keeping yaw snappy.
- ship.gd: new altitude-faded righting torque (spring-damper toward
belly-down, faded out by 3m so aerials keep full attitude freedom).
This is the grav-plating analogue of Rocket League's auto-righting and
helps human pilots land cleanly too.
- training_mode.gd: new ground_start_chance branch spawning ships level and
resting on the floor with a floor-level ball - the state the handling
stage's rewards are actually written for.
- generation5.py: ground-start-chance 0.50, air-drill-chance 0.20 -> 0.0.
Reward terms are left exactly as they were; they should finally pull in a
direction the ship can go.
Round 4 changed three things at once and two of them cut upright pressure:
grounded_upright_reward went to 0 and ground_tilt_penalty was cut 2.5x,
while the new uprightness multiplier only pays below GROUND_HANDLING_HEIGHT
*and* while moving forward *and* facing the ball - a far narrower slice of
ticks than the penalty it was meant to replace. Net pressure fell and
upright_fraction fell with it (0.268 -> 0.239 -> 0.238, the lowest of any
round). Restore ground_tilt_penalty to 0.05 and change nothing else, so
this is a genuine single-variable test of multiplier plus full tilt
pressure.
The conjunctive mechanism itself held up: forward_motion_fraction reached
its best sustained value (0.242) without goal_rate sagging, ep_rew_mean
turned positive for the first time (+0.28), and eval win rate hit 49% with
no reward hacking.
Also adds grounded_upright_fraction: a diagnostic, deliberately ungated
metric measuring uprightness over real floor-contact ticks instead of
sub-3m ticks. upright_fraction has never exceeded 0.331 across four rounds
and ~560M steps without cheating, and its denominator is dominated by
ballistic transit (airborne_fraction ~0.45, mean_altitude ~4.4m) where
attitude is not meaningfully controllable - so it likely cannot measure
what the 0.45 floor was meant to capture. Re-baseline that floor from what
this reports rather than from another round of reshaping.
Rounds 2 and 3 showed that tuning grounded_upright_reward's magnitude only
slides along a tradeoff instead of resolving it: at 0.015 upright_fraction
climbed to 0.331 while goal_rate sagged to 0.542 (then farmed outright at
0.696/0.366), and at 0.004 goal_rate climbed 0.569->0.604 while
upright_fraction went flat at ~0.26. An additive uprightness bonus is an
alternative to playing well, so the policy just picks whichever is cheaper
and no magnitude buys both behaviours.
Change the mechanism rather than the number: grounded_upright_reward drops
to 0, and uprightness becomes a multiplier inside the nose-led approach
term, which already requires moving forward at the ball. Parked-and-upright
and fast-but-sideways now both pay zero; only upright, forward, nose-on to
the ball pays full. forward-velocity-to-ball rises 0.06 -> 0.15 to offset
the ~2-3x expected-value cut from the new factor, and ground-tilt-penalty
drops 0.05 -> 0.02 now that uprightness is paid positively during play.
Delete the three blocked attempts and reset state to restart from the
Stage-3 foundation.
grounded_upright_reward at 0.015 overshot: four force-retries pushed
upright_fraction from 0.265 to a plateauing 0.331, then the fifth jumped it
to 0.696 (55% over the 0.45 floor) while goal_rate collapsed 0.542->0.366
and forward_motion_fraction fell 0.244->0.184 (vertical_thrust_mean went
negative) - the policy learned to sit pinned upright and farm the bonus
instead of chasing the ball. It was sized "comparable to
time_penalty/ball_distance_penalty" but at 0.015/tick it was actually above
ball_distance_penalty's 0.01/tick worst case, so idling near the ball beat
playing. Cut to 0.004/tick (episode ceiling ~7.2, below
ball_distance_penalty's ~18 worst case). Delete the five blocked attempts
and reset generation5_state.json so the next run starts fresh from the
Stage-3 foundation rather than continuing from the farming checkpoint.