# Fallow service kitchen

An editable, standalone reconstruction of the kitchen in [Fallow's head-chef POV](https://www.youtube.com/watch?v=Ipe9xJCfuTM). Built from multiple views in the selected video and the matching service-kitchen tour. The authored source and benchmark remain a separate study; a copyable platform environment uses the same native package.

![Kitchen aisle](review/iteration-final/aisle.png)

Open [the visual review](review/index.html), [the reference ledger](reference-ledger.md), or [the food close-up](review/iteration-final/food.png).

The standalone study is published at [robotgym.io/scenes/fallow-kitchen](https://robotgym.io/scenes/fallow-kitchen). The site navigation links to the review, editable downloads and test evidence. You can now [enter the platform environment and create a private copy](https://robotgym.io/environments/8ffa5828-2913-5447-be24-f9e9888736e6). The copy preserves the native scene; benchmark Python adapters and offline controllers remain separate. The [interaction benchmark](benchmark/README.md) adds five Franka task setups, repeatable evaluation, sensor observations and explicit model limits. The [published results](review/benchmark.html) distinguish demonstrated behavior from failed trials. The [training roadmap](training-roadmap.md) identifies the next calibration and controller work.

## Open and edit

| File | Purpose |
| --- | --- |
| `package/fallow-kitchen.blend` | Editable Blender master, including cameras, lights and relative image textures |
| `package/scene.usda` | Native USD entry point: scene hierarchy, rigid-body physics, joints, collision materials and semantic attributes |
| `package/visuals.usdc` | Referenced visual geometry, materials, lights and cameras |
| `package/scene-usd.zip` | Complete portable native package: USD layers and their texture dependencies |
| `package/scene.usdz` | Single-file USD package; physics still needs a compatible runtime |
| `package/scene.glb` | Visual preview/export; does not carry the native USD physics |
| `package/experiments/fluid-pour.usda` | Optional GPU particle experiment, with the room's movable props frozen |
| `package/experiments/jug-pour.usda` | Initial state for the optional jug experiment; `source/jug_qa.py` supplies its tipping motion |
| `source/manifest.json` | Object identities, masses, collision shapes, appliance estimates, joints and camera poses |
| `source/appliances.py` | Explicit appliance controller, estimated thermal models and bulk-water mass/energy ledger |

Unzip `scene-usd.zip` and open **`scene.usda`** in Isaac Sim. Keep the texture directory beside it. The default prim is `/FallowKitchen`, Z is up, and one unit is one metre. The package contains an environment; add a robot, task and policy separately. No production service or shared library entry is required to open these files.

## What has physical behavior

| Feature | Implemented behavior | Boundary |
| --- | --- | --- |
| 36 movable bodies | Mass, gravity, rigid contacts, CCD, separate physical material bindings; includes a loose lid, open pouring jug and oven door | Masses and friction are estimates, not measured equipment properties |
| Plates, bowls, GN pans, saucepans and stockpot | Compound walls and bases leave their interiors open | Collision geometry is simpler than the visual meshes; liquids need the separate particle experiment |
| Two refrigerated drawers | Prismatic joints, 39 cm travel limit, spring/damper drive and hollow tray geometry; optional thermostat/cooling model with open-drawer heat load | Other cabinet fronts are dressing; cooling parameters are estimates |
| Countertop oven | Hollow cavity, rack, fan grille and physically hinged door; optional thermostat, timer, door interlock and food surface/core thermal lag | Internals and controls are inferred, without a manufacturer-specific calibration |
| Food | Separate movable brioche halves, patty/toppings, lemon half, whole lemons and bread rolls | Rigid food; no cutting, squashing, tearing, melting or cooking chemistry |
| Gas, induction and salamander | Optional left-burner ignition/flame-failure control, induction pan/compatibility gating and a generic salamander thermostat/timer model | Only the instrumented stockpot receives burner heat; other jets and glowing coils remain dressing. No combustion or smoke transport |
| Stockpot heat | Optional two-node heat model with body/handle conduction, ambient cooling and position-dependent burner input | Run `thermal.py` explicitly through its adapter; the standard scene loader does not run it |
| Pouring/spilling | GPU PBD column test and kinematic jug test; bulk-water mixing, overflow and boiling ledger in the optional controller | Particle geometry and bulk thermodynamics are separate; no temperature or phase-change coupling between them |
| Knives, towels, food in fixed mise en place pans | Detailed visual dressing | Knife cutting, cloth deformation and ingredient mixing are not implemented |

The dry-contact baseline uses static/dynamic coefficients of 0.45/0.30 for steel, 0.60/0.45 for ceramics, 0.55/0.40 for plastic, 0.80/0.60 for food and 0.85/0.70 for the floor. These are configurable **assumptions**, with no wetness, grease, adhesion or temperature dependence. The body collision shapes determine inertia; they do not reproduce the exact mass distribution of each manufactured item.

The separate benchmark tests lid placement, hot-pan transfer, drawer retrieval and closure, pouring, and oven loading/control. See the [actual trial results](review/benchmark.html) before choosing a task; an implemented setup does not establish a successful policy. The [complete physics inventory](review/physics-audit/inventory.json) identifies every body, fixed prop, collider and decorative mesh.

## Detail and reference fidelity

The scene includes the veined pale-green stone pass, figured timber fascia, hanging ticket rail, individual orders, plate stacks, patinated wall panels, black ceiling, extraction baffles, fire-suppression pipework, refrigeration grilles/displays, gas/induction/solid cooking surfaces, twin fryer baskets, salamander, countertop oven and restaurant context.

Food detail includes a textured brioche crust and crumb, charred patty surface, draped cheese geometry, mushroom caps/gills, pickles/seeds, lemon rind/pith/segments and individual herb leaves. Toppings and recipe composition are plausible interpretations where the video does not resolve them. The model combines observations across service; it is not a reconstruction of one exact timestamp.

The second reference pass adds fitted pan lids, ladles with bowls, concentric trivet channels, a white linen tub with folded cloth, nested GN containers, rolled pan flanges, a blue-lidded shaker, wire whisk, red spatulas, individual chips in service cups, bent order slips and a spouted pouring jug. Cutting boards have directional knife wear; steel has wiping/brushing and selective heat discoloration. Four additional review cameras inspect these objects and the open oven. Details and placement were corrected through three further render passes.

Dimensions, inaccessible surfaces and appliance internals remain inferred. The kitchen is a detailed reference reconstruction, **not a surveyed digital twin**. Close-up food and surface materials still offer room for improvement; the video does not provide the measurements needed to claim an exact physical replica.

## Heat model

`source/thermal.py` exposes `Node`, `Link`, `Burner`, `ThermalNetwork` and `UsdThermalAdapter`. State uses kelvin, watts and joules. Links transfer equal and opposite energy; ambient exchange and burner input are accounted for in an energy ledger. The explicit solver substeps according to the smallest thermal time constant.

`kitchen_network()` represents an empty 1.2 kg stainless stockpot as a 1.10 kg body plus 0.10 kg handle. Specific heat is assumed to be 500 J/(kg·K); effective body/handle conductance is 1.6 W/K. The burner delivers up to an estimated 1,800 W to a centred pot. Current world pose determines the radial and vertical heat coupling. The controller defaults to off.

An application using this adapter must supply **PhysX world positions after each physics step**, rather than stale initial USD transforms:

```python
from thermal import STOCKPOT, kitchen_network, UsdThermalAdapter

heat = UsdThermalAdapter(stage, kitchen_network())
values = heat.tick(dt, {STOCKPOT: current_stockpot_world_position}, {"gas_left": 0.5})
```

Temperature telemetry is written into the USD session layer. Nothing is executed automatically from the scene asset, and this change does not enable the adapter in the existing RobotGym worker.

Run `python source/thermal_demo.py` from this directory to reproduce [the heat curve](review/thermal/temperature-curve.svg), CSV and energy-balance receipt. It heats the empty pot for 90 seconds, moves it away with the burner still on, then follows cooling and handle lag. It is not a boiling, food-doneness or spatial heat-flow model.

## Appliance controls and bulk water

`source/appliances.py` exposes `KitchenMachines`, `UsdKitchenAdapter`, `Chamber`, `BurnerControl` and `Water`. All machines start off. Commands validate ranges before changing state. The adapter writes telemetry and the left flame's visibility into the USD session layer; opening a USD asset alone does not execute Python. Existing RobotGym workers do not automatically install this behavior.

```python
from appliances import KitchenMachines, UsdKitchenAdapter

machines = KitchenMachines.from_manifest()
adapter = UsdKitchenAdapter(stage, machines)
machines.command("oven", enabled=True, setpoint_c=180, timer_s=600, loaded=True)
machines.command("gas_left", power=0.5, ignite=True)
adapter.set_oven_door(70)  # PhysX angular drive target, degrees
state = adapter.tick(dt, pot_position=current_stockpot_world_position,
                     oven_open=measured_oven_angle_degrees / 100,
                     drawer_open=measured_drawer_open_fraction,
                     induction_pan=False, induction_compatible=False)
```

The caller supplies actual post-step poses, normalized door/drawer opening and pan sensors. These inputs are an integration interface, not automatically inferred perception. The native acceptance test uses measured PhysX door rotation to exercise the heater interlock.

| Device | Commands / modeled behavior |
| --- | --- |
| `oven` | `enabled`, 30–260 °C `setpoint_c`, `timer_s`, `loaded`; thermostat hysteresis, open-door heater inhibition and increased heat loss, two food temperature nodes |
| `salamander` | `enabled`, 30–450 °C `setpoint_c`, `timer_s`, `loaded`; generic single-zone thermal model, without spatial radiation or independent left/right heating zones |
| `fridge` | `enabled`, 1–8 °C `setpoint_c`; estimated cooling power and extra heat ingress when a drawer opens |
| `gas_left` | `power` 0–1, `ignite`, `flame_out`, `reset`; ignition required, flame-failure fault, position-dependent stockpot heat input |
| `induction` | `power` 0–1; requires pan-present and compatible-pan sensors, plus the stockpot's position at the hob for pot heat transfer |

The optional water reservoir starts with 250 g in the stockpot. It exchanges heat with the metal, reaches an atmospheric boiling plateau at 100 °C, then consumes latent heat as mass evaporates. Dry-out returns unabsorbed energy to the vessel. `Water.pour_into()` transfers enthalpy, mixes temperatures and explicitly returns overflow mass and energy. Water uses constant 4,180 J/(kg·K), 2,256,000 J/kg latent heat and 1,000 kg/m³ density: engineering approximations, not a full [IAPWS water-property model](https://www.nist.gov/srd/nistir-5078).

Bulk water does **not** update rigid-body mass, sloshing, particle count, fluid temperature fields, steam geometry or wet friction. Food temperature nodes have estimated capacities and conductances; they do not predict doneness or cooking chemistry. Knob meshes are visual controls; a UI or robot interaction bridge must call the command API. Fryers, solid tops, dish warmers and other visible equipment do not gain control logic from this adapter.

Run `python source/appliance_demo.py` for the [behavior curves](review/appliances/behavior.png), [event data](review/appliances/events.csv) and [energy receipt](review/appliances/result.json). The 25-minute demonstration includes flame failure, door opening, timers, refrigeration and water boiling. Eight appliance tests supplement the seven original thermal tests.

## Liquid experiments

`source/fluid_qa.py` creates two 3,456-particle columns: one over the authored open GN pan and one outside it. It measures containment and spill positions after five simulated seconds. All room props are frozen for this focused test. The generated `fluid-experiment.usda` is an optional layer referencing the normal package; it is not the default entry point.

`source/jug_qa.py` additionally places particles inside the authored open jug, holds it upright, then tips the jug with a prescribed kinematic rotation over an empty GN pan. It measures retention before tipping, the actual PhysX jug angle, final target containment and floor escapes. This tests the vessel geometry and fluid contact, without a robot controller.

These require GPU PhysX and the Isaac particle extensions. The existing RobotGym runner explicitly selects CPU dynamics, so **the experiments cannot be enabled merely by importing the ordinary scene there**. Particle appearance, viscosity presets and collision spacing are approximations. The particle solver has no advection of temperature, evaporation, boiling, foam, splashing spray shader, or fluid/food chemical coupling; those are distinct from the bulk-water ledger above.

The column run retained **95.5%** of the pour particles in the target pan. The deliberate spill retained none there. One spill particle travelled outside the finite floor; no particle was found below the floor while still inside its bounds. See the [particle-position plot](review/native/fluid-positions.png), [raw receipt](review/native/fluid.json) and [boundary audit](review/native/fluid-boundary-audit.json). This is a focused feasibility test with approximately 0.75 L per stream.

The jug run retained **100% of 1,157 particles** upright, then delivered them all into the target pan after the prescribed 105° tip. Nominal volume is 144.625 ml. See the [jug plot](review/native/jug-positions.png) and [receipt](review/native/jug.json). This is one successful controlled trajectory, not a general robot pouring benchmark. The optional USD layer stores the initial state; the runner supplies the movement.

NVIDIA documents [GPU PBD particles and their CPU limitation](https://docs.omniverse.nvidia.com/kit/docs/omni_physics/latest/dev_guide/particles/particles.html). [Flow](https://docs.omniverse.nvidia.com/extensions/latest/ext_simulation.html) is a separate fire/smoke system. Adding Flow, deformable food and cutting would be separate solver/integration work; those capabilities are not implied by the rendered flame or detailed food meshes.

## Rebuild

Validated authoring environment: Blender 5.2.1 LTS, Python with `requirements-authoring.txt`, macOS Metal rendering. Blender is an external prerequisite. Set `BLENDER` to your installed executable; the paths below are relative to this scene directory.

```sh
python -m pip install -r requirements-authoring.txt
python source/make_textures.py
python source/food_textures.py
python source/refinement_textures.py
"$BLENDER" -b --factory-startup --python source/build_scene.py -- --draft --iteration rebuilt --render aisle,food,burner
python source/author_physics.py
python source/validate_package.py
python -m unittest discover -s source -p 'test_*.py' -v
python source/thermal_demo.py
python source/appliance_demo.py
python source/plot_evidence.py
```

For higher-quality review views from the saved master:

```sh
"$BLENDER" -b package/fallow-kitchen.blend --python source/render_scene.py -- --views aisle,reverse,service,detail,hotline,overview,plan,food,burner,salamander,reference_pass,reference_island,utensils,oven --iteration final --samples 128 --width 1800
```

After changing geometry, rerun the builder/exporter **and** `author_physics.py`; the manifest and native layer must describe the same build. `author_physics.py` checks the entire USD dependency closure and regenerates ZIP/USDZ packages.

For native acceptance, run `source/native_qa.py` inside an Isaac worker image with this directory mounted at `/qa` and a writable `/qa/native-review`. It settles bodies, drops a sphere into an open pan, opens/closes both drawers and the oven, and uses actual oven motion to test heater inhibition and recovery. `source/fluid_qa.py` and `source/jug_qa.py` use the same mount in separate GPU processes. The wrapper below manages mounts and copies receipts into `review/native`.

For the native image check, run `/qa/source/native_render_qa.py --final` in that image. It enables RTX Path Tracing **at startup**, reapplies the mode after loading the stage, warms up the GPU pipeline, then renders four review cameras with 64 samples per pixel and OptiX denoising. Its receipt records the actual renderer mode and rejects black or substantially empty output. Merely changing the active mode after startup can leave the requested renderer unavailable; retaining the first cold accumulation can also produce an empty image. See NVIDIA's [startup renderer switches](https://docs.omniverse.nvidia.com/materials-and-rendering/latest/rtx-renderer_pt.html) and [Replicator render settings](https://docs.omniverse.nvidia.com/kit/docs/omni_replicator/1.13.30/source/extensions/omni.replicator.core/docs/API.html).

Native light exposure is matched by visual review, not calibrated photometry. Materials and environment lighting are inside the default prim so they survive importing the kitchen as a USD reference. The main gallery contains Blender Cycles renders, with separate native views for comparison. Real-time simulator output can have substantially more noise; these still captures are not a rendering-throughput benchmark.

The included wrapper runs each check in an isolated container on a Linux NVIDIA GPU host:

```sh
source/run_native_check.sh physics
source/run_native_check.sh fluid
source/run_native_check.sh jug
source/run_native_check.sh render
```

Set `ISAAC_IMAGE` if your worker image has a different tag. The image, Docker/NVIDIA runtime and host Python 3 are external prerequisites. The render command mounts the installed driver's `/usr/share/nvidia/nvoptix.bin` read-only; set `FALLOW_OPTIX_WEIGHTS` if its location differs. These NVIDIA weights are a runtime dependency, not a scene asset. Shader caches stay outside git. The wrapper mounts source and packages read-only, copies the resulting evidence into `review/native`, and returns failure if the expected passing receipt is absent.

## Scope kept separate

The reference scene remains independent of robot embodiment. The adjacent `benchmark/` directory contains the Franka setup, policy boundary, scoring, models and evaluation runner. Shared library entries and accounts/credentials remain outside this scene asset. Publication serves this directory as a standalone study; it does not automatically install the behavior adapters in production simulation workers. The repository stores the source, editable master, portable packages and review evidence. Raw reference videos are kept outside git. One small rectified stone-surface crop from the selected video is included and explicitly documented in the reference ledger; other textures are generated from the checked-in scripts.
