vikash-nuvai
feat: complete tiffin packing OpenEnv environment with 3 tasks, VLM, grader, and inference
bbc1784
# Copyright (c) 2026 CtrlAltWin Team
"""
Task Definitions — Easy, Medium, Hard difficulty levels.
Each task defines what food items are on the table, what containers are
available, what constraints are active, and how many steps the agent gets.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import List, Optional
from .simulation.engine import Container, FoodItem
from .vlm.classifier import FoodClassifier
@dataclass
class TaskConfig:
"""Configuration for a single task."""
task_id: str
description: str
food_items: List[FoodItem]
containers: List[Container]
constraints: List[str]
max_steps: int
seed: Optional[int] = None
_vlm = FoodClassifier()
def _make_food(id: int, name: str) -> FoodItem:
"""Create a FoodItem from the VLM database."""
attrs = _vlm.classify(name)
return FoodItem(
id=id,
name=name,
food_type=attrs["type"],
volume_ml=attrs["volume_ml"],
temperature=attrs["temperature"],
fragility=attrs["fragility"],
preferred_container=attrs["preferred_container"],
color=attrs.get("color", "unknown"),
special_notes=attrs.get("special_notes", ""),
)
def get_task_config(task_id: str, seed: Optional[int] = None) -> TaskConfig:
"""Get task configuration by ID."""
tasks = {
"easy": _task_easy,
"medium": _task_medium,
"hard": _task_hard,
}
if task_id not in tasks:
raise ValueError(
f"Unknown task_id '{task_id}'. Available: {list(tasks.keys())}"
)
config = tasks[task_id](seed)
return config
def _task_easy(seed: Optional[int] = None) -> TaskConfig:
"""
Task 1 — Basic Packing (Easy)
2 food items, 2 containers. Just match food type to container type.
Rice (solid) → open/deep container, Sambar (liquid) → sealed container.
"""
return TaskConfig(
task_id="easy",
description=(
"Basic Packing: You have 2 food items (rice and sambar) and "
"2 containers (one sealed, one open). Place each food item in "
"a compatible container. Liquids must go in sealed containers."
),
food_items=[
_make_food(1, "rice"),
_make_food(2, "sambar"),
],
containers=[
Container(
id=1,
name="Sealed Round Container",
container_type="sealed_round",
capacity_ml=300,
),
Container(
id=2,
name="Flat Open Container",
container_type="flat_open",
capacity_ml=400,
),
],
constraints=["type_match"],
max_steps=12,
seed=seed,
)
def _task_medium(seed: Optional[int] = None) -> TaskConfig:
"""
Task 2 — Efficient Packing (Medium)
4 food items, 3 containers. Must match types AND avoid overflow.
Hot/cold separation matters.
"""
return TaskConfig(
task_id="medium",
description=(
"Efficient Packing: You have 4 food items (rice, sambar, chapati, "
"pickle) and 3 containers. Place each item correctly:\n"
"- Match food type to container type (liquids → sealed)\n"
"- Don't overflow containers (check volumes!)\n"
"- Keep hot and cold items separate"
),
food_items=[
_make_food(1, "rice"),
_make_food(2, "sambar"),
_make_food(3, "chapati"),
_make_food(4, "pickle"),
],
containers=[
Container(
id=1,
name="Sealed Round Container",
container_type="sealed_round",
capacity_ml=200,
),
Container(
id=2,
name="Flat Open Container",
container_type="flat_open",
capacity_ml=300,
),
Container(
id=3,
name="Deep Box Container",
container_type="deep_box",
capacity_ml=350,
),
],
constraints=["type_match", "no_overflow", "temperature_separation"],
max_steps=20,
seed=seed,
)
def _task_hard(seed: Optional[int] = None) -> TaskConfig:
"""
Task 3 — Smart Packing (Hard)
6 food items, 4 containers. Full constraint set:
type match, overflow, temperature, fragility, flavor mixing.
Key challenges:
- Curd (cold) ≠ hot items in same container
- Papad (fragility=0.9) must not be crushed
- Curry + sambar both liquid+hot → total 300ml but sealed_round only 250ml!
- Must split liquids across containers
"""
return TaskConfig(
task_id="hard",
description=(
"Smart Packing: You have 6 food items and 4 containers. This is a "
"complex meal with many constraints:\n"
"- Match food type to container type\n"
"- Don't overflow (watch the math!)\n"
"- Separate hot and cold items\n"
"- Don't crush fragile items (papad, chapati)\n"
"- Consider flavor isolation (pickle, chutney)\n"
"\nItems: rice, sambar, curd, chapati, papad, curry\n"
"Containers: sealed_round (250ml), flat_open (200ml), "
"deep_box (400ml), small_sealed (100ml)"
),
food_items=[
_make_food(1, "rice"),
_make_food(2, "sambar"),
_make_food(3, "curd"),
_make_food(4, "chapati"),
_make_food(5, "papad"),
_make_food(6, "curry"),
],
containers=[
Container(
id=1,
name="Sealed Round Container",
container_type="sealed_round",
capacity_ml=250,
),
Container(
id=2,
name="Flat Open Container",
container_type="flat_open",
capacity_ml=200,
),
Container(
id=3,
name="Deep Box Container",
container_type="deep_box",
capacity_ml=400,
),
Container(
id=4,
name="Small Sealed Container",
container_type="small_sealed",
capacity_ml=100,
),
],
constraints=[
"type_match",
"no_overflow",
"temperature_separation",
"fragility_ordering",
"flavor_isolation",
],
max_steps=30,
seed=seed,
)
def list_tasks() -> List[str]:
"""Return list of available task IDs."""
return ["easy", "medium", "hard"]