Skip to content

Add pybullet_bridge glue-construction domain#51

Merged
yichao-liang merged 2 commits into
pr5-configsfrom
pr6-bridge
Jul 7, 2026
Merged

Add pybullet_bridge glue-construction domain#51
yichao-liang merged 2 commits into
pr5-configsfrom
pr6-bridge

Conversation

@yichao-liang

@yichao-liang yichao-liang commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Stacked PR - merge bottom-up. This branch targets the PR below it, so its diff shows only its own changes.

  1. Infra: PyBullet env-leak fix, adaptive-thinking SDK, scene-snapshot guard #46 Infra fixes (base: master)
  2. Domino: goal-NL cleanup + relax Toppled threshold to 30 deg #47 Domino tuning (base: Infra: PyBullet env-leak fix, adaptive-thinking SDK, scene-snapshot guard #46)
  3. SysID: PHYSICAL_PARAMS system identification via free-running rollout fit #48 SysID / PHYSICAL_PARAMS (base: Domino: goal-NL cleanup + relax Toppled threshold to 30 deg #47)
  4. Fan env: grid-free agent + GT hybrid (wind) simulator #49 Fan env (base: SysID: PHYSICAL_PARAMS system identification via free-running rollout fit #48)
  5. predicatorv3 configs: thin launchers, exp splits, shared use_gt_helpers #50 predicatorv3 configs (base: Fan env: grid-free agent + GT hybrid (wind) simulator #49)
  6. Add pybullet_bridge glue-construction domain #51 Bridge domain (base: predicatorv3 configs: thin launchers, exp splits, shared use_gt_helpers #50)
    New pybullet_bridge glue-construction domain: weld-on-cure bridge building. Top of the stack. Self-contained new env + ground-truth models + docs/tests; only shared touch-points are settings.py, utils.py, skill_factories/base.py, and the predicatorv3 env list.

Contents

  • envs/pybullet_bridge.py + ground_truth_models/bridge/{gt_simulator,gt_simulator_po,nsrts,options,processes}.py
  • New pick/place skill-factory support in skill_factories/{base,pick,place}.py
  • exp_bridge.yaml launcher, docs/envs/bridge/ (README, init snapshots, oracle solve videos)
  • tests/envs/test_pybullet_bridge.py, tests/approaches/test_oracle_process_planning_bridge.py

Merges last.

New slow-process domain: build an n-shaped bridge by gluing blocks
with a pickable glue bottle. Cured joints create body-to-body
JOINT_FIXED welds, so the hidden cure process has a kinematic
consequence: welded assemblies move as one rigid unit. Two specs:
"simple" (4 blocks / 3 joints) and "full" (7 blocks / 6 joints).

- envs/pybullet_bridge.py: env with per-face glue/cure/attached
  features, weld lifecycle (snap to yaw-only relative orientation and
  ideal dz to prevent constraint-vs-contact creep), visual glue
  patches, jittered-grid staging, both task specs.
- ground_truth_models/bridge/: options (face-targeted glue dab skill,
  shared place skill), endogenous/exogenous processes with
  abstract-physical alignment guards (Loose/Resting/TopFree,
  single-order Attached in CureLateralJoint), NSRT mirrors, and FO/PO
  answer-key simulators.
- skill_factories: pick gains approach_open/anchor_lift/lift_dz;
  place gains param_defs override and compensate_held_offset; BiRRT
  excludes welded partners of the held object from collision bodies.
- utils: wait_option_max_steps bail-out for Waits stranded by cures
  that complete during the preceding option.
- configs: parked bridge menu entry in envs/all.yaml plus
  exp_bridge.yaml launcher.
- tests: oracle_process_planning E2E on the simple spec and a weld
  lifecycle env test.
- docs/envs/bridge: init-state renders for both specs and oracle
  solve videos.

Oracle results: simple 5/5, 5/5, 3-4/5 (seeds 0-2), PO 3/3, full 1/3
with the first task solving the complete 7-block build end-to-end.
@yichao-liang yichao-liang merged commit f4c35cb into pr5-configs Jul 7, 2026
14 checks passed
yichao-liang added a commit that referenced this pull request Jul 7, 2026
…dation) (#52)

* Fix PyBullet env leak in get_gt_options: share skill simulator/robot worlds

Every get_gt_options() call leaked ~145MB of PyBullet C-heap that could
never be freed (nothing calls p.disconnect): _build_skill_config built a
fresh motion-planning simulator env per call when
skill_phase_use_motion_planning is on, and get_options additionally ran
env_cls.initialize_pybullet() per call just to obtain the SkillConfig
robot handle (leaked even with the flag off). min_block_utils'
_get_push_option calls get_gt_options once per K* probe, so domino
min-block task generation leaked 10+GB per run and OOM'd a 16GB machine.

Fix: process-wide shared_skill_simulator/shared_skill_robot caches (one
world per env class) in skill_factories/base.py, used by the five
factories that had the pattern (domino, boil, grow, coffee, fan).
Sharing is safe because _plan_with_simulator re-syncs the full state via
_set_state before every query. update_config_with_parser clears the
caches on config changes (env construction reads CFG), looked up via
sys.modules so processes that never use skills never load pybullet.

Verified: get_gt_options x10 flat (was +145MB/call); K* probe loop
byte-flat with identical K* results (was +140MB/probe); min-block task
generation 2.53GB -> 0.64GB RSS.

* Agent SDK: adaptive thinking replaces budget_tokens (removed on sonnet-5)

With agent_sdk_model_name now claude-sonnet-5, the old
thinking={type: enabled, budget_tokens: N} config is rejected with a 400
on every request (removed API surface, not just deprecated). Pass
thinking={type: adaptive} at all four sandbox call sites and drop the
agent_sdk_thinking_budget_tokens setting; deliberation depth is now
controlled solely via agent_sdk_reasoning_effort.

* Scene images: stop replans clobbering init snapshots; render explore inits

_render_initial_state_image fired on every agent query -- including
mid-episode replans rooted at the current, partially-executed state --
and wrote the same task{N}_initial_state.png, so post-hoc "init" images
showed already-toppled dominoes. A per-episode render counter (reset in
reset_for_new_episode, which CogMan never calls on replans) now names
replan renders task{N}_replan{K}_state.png, and _initial_image_section
references whichever file the current query actually saved, so replan
prompts point at the current scene instead of the stale init.

Also extract the render logic into bilevel_sketch.save_task_state_image
and use it from AgentBilevelExplorer, which now saves
train_task{N}_initial_state.png and injects the same "read this image
first" prompt section into explore queries that test solves get.

* Satisfy CI docformatter on touched files

* Remove hint sentences from domino goal NL descriptions

Drop the {hint} slot in _HEAVY_GOAL_NL (90-degree-turn and
curve-around-obstacles hints) and the turn hint in the min-block
L-task goal; also normalize an em dash to -- in the min-block goal.

* Domino goal NL: allow zero blues so the believed-optimal plan is stated

"Move the blue dominoes ... AS FEW as possible" presupposes moving at
least one blue, which forecloses the 0-blue direct push -- exactly the
plan a miscalibrated (over-reach) planner believes optimal on min-block
tasks kept with believed K* < true K*. Agents therefore never validated
the believed-physics optimum, dodged the differentiation trap with a
conservative 1-blue bridge, and early-stopped before sysID could run.
Reword all goal strings to "Arrange the blue dominoes ... (possibly
none)" so under-building is the stated optimum, not a rule violation.

Note: cached tasks in saved_datasets/domino_min_block_tasks embed the
old goal_nl; delete them to regenerate with the new wording.

* Domino: relax Toppled threshold to 30 deg to count propped leaners

A domino free-stands only up to atan(depth/height) ~= 5.7 deg, so a
lean past 30 deg is sustained solely by resting on another body, i.e.
the domino was genuinely knocked over. The old 72 deg criterion missed
propped leaners (e.g. a target at roll 0.608 rad wedged on the fallen
pusher), forcing relay placements for spans a direct push handles.

All call sites read comp.fallen_threshold, so the goal predicate,
Tilting boundary, movable-block counting, and min-block believed-K*
certification stay mutually consistent.

* Add PHYSICAL_PARAMS system identification via free-running rollout fit

Teacher-forced single-step fitting cannot see physical parameters of
momentum-driven dynamics (states carry pose but no velocity), so
friction/restitution were invisible to the existing objective. New
code_sim_learning/physical_sysid.py fits agent-declared PHYSICAL_PARAMS
by matching free-running base-sim rollouts of full trajectories, jointly
with rule PARAM_SPECS in one posterior, and reports per-parameter
identifiability (posterior contraction) instead of regularizing it away.

- BaseEnv.get_physical_param_info / apply_physical_param_overrides:
  envs advertise tunable physics and accept sticky in-place overrides;
  implemented for pybullet_domino (friction, restitution, mass,
  rolling/spinning friction).
- Synthesis prompt gains an optional PHYSICAL_PARAMS section built from
  the env's revealed menu; evaluate_step_fit switches to the joint
  rollout fit when the artifact declares PHYSICAL_PARAMS and appends
  the identifiability report.
- Physics-only artifacts (no PROCESS_RULES) are now valid; identified
  values are applied to the planning base env so refinement and test
  planning use calibrated physics.
- _solve_lm grows a diff_step override: simulation residuals need a
  coarser finite-difference step or the Jacobian is identically zero.
- New CFG code_sim_learning_rollout_num_mcmc_steps keeps the costly
  rollout-fit MCMC budget separate (default 0 = LM point fit).

* SysID rollouts: zero arm joint velocities between rollout evaluations

resetBaseVelocity alone leaves articulated joint velocities behind, and
_set_state's per-component diff skips joints whose positions already
match, so consecutive rollouts on the shared fit env inherited up to
~1.8 rad/s of residual arm momentum. Position control overrides it
within a step or two, so this is hygiene rather than the (engine
internal) source of the observed same-theta SSE jitter, but rollouts
genuinely must start at rest.

* SysID identifiability: discount probe curvature below the noise floor

The prior-scale curvature probe measured the SSE increase against a
single MAP evaluation, so on chaotic data the same-theta evaluation
jitter itself read as curvature and every parameter was declared
identified (run_20260705_203314: ~5k d_sse inside a ~8k jitter,
contraction 0.00 on all params, false confidence in friction 0.5).
Evaluate the MAP SSE three times, take the median, and subtract the
observed spread from every perturbation response before it counts as
curvature; a flat-after-discount direction reports NOT identified.

* SysID fit: truncate settled trajectory tails and grid-seed the LM start

Two measured blockers on real domino data (true friction 0.1, planning
0.5):

- A free-running rollout diverges chaotically from the recording over
  hundreds of contact steps, and the long settled tail re-scores that
  accumulated divergence every step: on full 500-step trajectories the
  SSE at the TRUE friction exceeded the SSE at the wrong one. Cutting
  each trajectory after its last observed scored-feature motion (plus a
  settle margin) restores the signal: a clean top-edge push goes from
  ratio 0.9 to ~5e4 wrong/true SSE.

- The SSE landscape is flat above friction ~0.5 (topple reach
  saturates), so LM's finite differences see no gradient from the
  declared init and the fit stalls even on clean data. A coarse
  per-parameter grid sweep relocates the LM starting point into the
  best-scoring basin; the Gaussian prior stays centered on the declared
  init.

Together these recover friction 0.07-0.08 from a single clean recorded
push (init 0.5); previously the fit returned the init unchanged.

* SysID robustness: trim untrustworthy trajectories, guard param application

The fit consumed whatever exploration recorded, and applied whatever it
fitted. Both trusted too much (run_20260706_111805: a chaotic scraping
push dragged the pooled friction fit to 0.34, and rolling/spinning
friction were applied at arbitrary values their own identifiability
report called NOT identified). Three layers, each verified against
real recorded episodes:

- Explainability trimming (min_explainable_rms): judge each trajectory
  by its best-achievable RMS over a candidate param grid — judged
  against its own best params, not the pooled fit, which chaos poisons
  — and drop recordings no parameters can explain before fitting.
- Consistency loop: at the joint fit every survivor must fit nearly as
  well as its own best; on disagreement (a recording can be
  accidentally explainable at WRONG params, e.g. a quiet shove at
  friction ~1.34 vs the clean topple's 0.1) drop the survivor with the
  largest best-RMS and refit, anchoring on the cleanest data.
- select_trustworthy_params: apply a fitted physical value to the
  planning env only when its identifiability verdict is at least
  weakly identified; keep the declared init otherwise. If nothing
  survives trimming, the result is pinned at the declared inits so a
  false-positive verdict on chaotic data cannot leak arbitrary values.

Callers compute post-SSE and the identifiability probe on the
SURVIVING trajectories only. E2E on real recordings: chaotic-only data
keeps the declared inits with honest NOT-identified verdicts; chaotic
pooled with one clean topple trims the chaos and recovers friction
0.0706 (true 0.1) plus rolling_friction 0.0078 (true 0.006).

* sysID: fit scale-like physical params in log-space

run_20260706_171526 fit friction 0.0114 for a true 0.1: the linear
grid over [0.01, 2.0] has no candidate between 0.01 and 0.29, the
sweep jumped to the 0.01 endpoint, LM saw no gradient across the flat
low-friction basin, and the linear curvature probe blessed the
bound-hugging MAP as identified. The believed sim ended up 9x too
slippery and drop-place plans verified in sim but failed in the real
env (the 2/5 final-test failures).

ParamSpec gains scale="log" for positive scale-like parameters. The
optimizer runs in z = log(theta): geometric grid sweeps (a candidate
lands at ~0.098), relative LM finite-difference steps, a log-normal
prior (x2 up and x2 down equally plausible), log-space identifiability
contraction, and fit-space Laplace/perturbation ensembles. Everything
simulator- and caller-facing stays in linear units; FitResult samples
stay external while jacobian/prior_sigma live in fit space (scales
records the mapping).

The domino env registry marks friction, spinning_friction, and mass as
log-scale (restitution and rolling_friction keep lo=0, stay linear),
and the approach/tool paths stamp the registry scale onto the
agent-declared PHYSICAL_PARAMS so agents need not know about it.

E2E-verified on a same-engine cascade at true friction 0.1 from init
0.5: log-scale recovers 0.0987 (SSE 0.705 -> 0.0005); the old linear
path reproduces the 0.0100 endpoint failure on identical data.

* Satisfy CI checks (autoformatters + pylint docstrings) on touched files

* Fan: run the agent grid-free via oracle-injected grid helpers

Mirror the domino helper-object design so the fan agent plans over a
physical-only vocabulary while the loc/side grid is injected for the
oracle / process-planning approaches only.

- New ground_truth_models/fan/{types,predicates}.py: the loc/side helper
  types and the grid predicates (BallAtLoc/ClearLoc/SideOf/FanFacingSide/
  OppositeFan), plus augment_{task,state}_with_helper_objects that rebuild
  the exact task grid (coords encoded in loc names) and rewrite the goal
  BallAtTarget -> BallAtLoc.
- pybullet_fan.py: drop loc/side from types and the grid predicates from
  the env; add the physical BallAtTarget goal predicate; surface the
  target coordinates through per-task goal_nl; reconstruct injected
  loc/side features from object names. Two-table workspace with y_ub=2.1
  and front-anchored robot/switches so the arm never reaches into the grid.
- ground_truth_models/__init__.py: add the augment_state_with_helper_objects
  hook (base no-op + dispatcher).
- process_planning_approach.py: re-derive helper objects on every execution
  state before abstracting, so the closed-loop oracle keeps evaluating
  BallAtLoc during execution. No-op when helpers are disabled.

* Fan GT hybrid simulator: model the wind that base sims skip

Unlike domino (plain rigid-body toppling, no-op GT sim), fan applies its
wind in _domain_specific_step, which skip_process_dynamics=True base sims
never run - so the GT simulator must reproduce the ball's wind-driven
motion itself. Calibrated against the real env: constant 0.00228 m/action
ball speed, zero coasting, sphere-overhang wall parking, boundary clamp
from the target-inferred grid. Params ball_speed + wall_clearance (hard
hinge clamps keep the LM Jacobian informative). Unblocks the
agent_oracle_hybrid_sim arm; lockstep hybrid-vs-real tests stay within
14.6mm << the 40mm goal tolerance.

* Satisfy CI checks (autoformatters + pylint) on touched files

* Default code_sim_learning_num_mcmc_steps to 0; prune redundant agents.yaml flags

MCMC was already disabled via --code_sim_learning_num_mcmc_steps 0 in
every experiment config; make that the default (LM point fit only).
Drop the five flags in the active agent_po_predicate_invention_al arm
that now just restate settings defaults (option_model_use_gui,
agent_bilevel_log_state, agent_sim_learn_oracle_sim_program,
agent_sim_learn_oracle_sim_params, code_sim_learning_num_mcmc_steps).

agents.yaml also carries the arm toggling from the sysid work in
progress: agent_po_predicate_invention_al re-activated, the
agent_base_sim_no_learning / agent_oracle_hybrid_sim arms commented
out.

* Configs: unpark friction domino env for the sysID experiment; keep videos

Swap the active min-block arm back from the heavy-block variant to the
friction-mismatch domino env, and enable failure/test videos in common
launch args for run inspection.

* predicatorv3 configs: thin launchers over a canonical approaches menu

Extract every approach ("arm") into approaches/all.yaml, parked with
SKIP: True, and turn the launchers into thin includes that only un-skip
the arm(s) they run plus per-experiment ENVS overrides:
- oracle.yaml now includes approaches/all.yaml and flips oracle SKIP: False
- agents.yaml -> exp.yaml (the "ours" arm launcher)
- predicator_v3.yaml removed (its approach defs now live in the menu)

* predicatorv3 envs: enable the fan env; park the domino sysID env

Un-comment the pybullet_fan block in envs/all.yaml so the fan oracle runs
on this branch, and set the friction domino env SKIP: True (reversing the
sysID unpark) so only fan runs here.

* Add shared use_gt_helpers flag for both planning families

Rename the process-planning-only process_planning_use_gt_helpers to a
general CFG.use_gt_helpers read by both the process-planning and the
agent-planning approaches, so an "agent-with-grid" ablation can hand the
LLM agent the same oracle scaffolding (domino/fan grid loc/side types +
grid predicates). Default False; the oracle still hard-overrides to True.

- settings.py + 3 ExoPredicator configs: rename the flag.
- ground_truth_models: extract merge_gt_helper_types / merge_gt_helper_predicates
  (name-collision precedence) as the single definition of the helper-vocab
  merge; process_planning_approach now calls them.
- agent_planner_approach (base of every agent arm): when use_gt_helpers,
  merge helper types/predicates into the vocabulary before the agent
  session is built, augment the solved task with the grid + oracle goal,
  and re-derive the grid on every execution state for the policy's and the
  option model's abstraction (Wait-on-atom-change). Exploration-time
  augmentation is left as a follow-up.
- fan/domino augment_task_with_helper_objects: preserve goal_nl (the agent
  needs the NL goal even though the symbolic goal becomes BallAtLoc).

* predicatorv3: split per-experiment launchers exp_domino / exp_fan

Env selection now mirrors the approaches menu: every env in envs/all.yaml
is parked with SKIP: True and each thin launcher un-skips exactly the env
+ arm it runs. exp_domino.yaml reproduces the old agents.yaml run
(domino friction-sysID env x agent_po_predicate_invention_al) and
exp_fan.yaml reproduces the fan branch's exp.yaml run (fan env x
agent_oracle_hybrid_sim); both verified flag-for-flag identical to their
predecessors via generate_run_configs. oracle.yaml keeps running the fan
oracle and now says so explicitly.

* Add pybullet_bridge glue-construction domain (#51)

* Add pybullet_bridge glue-construction domain

New slow-process domain: build an n-shaped bridge by gluing blocks
with a pickable glue bottle. Cured joints create body-to-body
JOINT_FIXED welds, so the hidden cure process has a kinematic
consequence: welded assemblies move as one rigid unit. Two specs:
"simple" (4 blocks / 3 joints) and "full" (7 blocks / 6 joints).

- envs/pybullet_bridge.py: env with per-face glue/cure/attached
  features, weld lifecycle (snap to yaw-only relative orientation and
  ideal dz to prevent constraint-vs-contact creep), visual glue
  patches, jittered-grid staging, both task specs.
- ground_truth_models/bridge/: options (face-targeted glue dab skill,
  shared place skill), endogenous/exogenous processes with
  abstract-physical alignment guards (Loose/Resting/TopFree,
  single-order Attached in CureLateralJoint), NSRT mirrors, and FO/PO
  answer-key simulators.
- skill_factories: pick gains approach_open/anchor_lift/lift_dz;
  place gains param_defs override and compensate_held_offset; BiRRT
  excludes welded partners of the held object from collision bodies.
- utils: wait_option_max_steps bail-out for Waits stranded by cures
  that complete during the preceding option.
- configs: parked bridge menu entry in envs/all.yaml plus
  exp_bridge.yaml launcher.
- tests: oracle_process_planning E2E on the simple spec and a weld
  lifecycle env test.
- docs/envs/bridge: init-state renders for both specs and oracle
  solve videos.

Oracle results: simple 5/5, 5/5, 3-4/5 (seeds 0-2), PO 3/3, full 1/3
with the first task solving the complete 7-block build end-to-end.

* Satisfy CI checks (autoformatters + mypy + pylint) on touched files
@yichao-liang yichao-liang deleted the pr6-bridge branch July 7, 2026 19:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant