Simulation · Published v2
Golden Fondue
A Swiss-cheese wedge warms on a passive dark platform, softens and slowly spreads into a golden puddle. The heater input (0 off, 1 full heat) directly controls heating and softening; the platform stays dark and supplies no heat; conservative downward and sideways fluxes retain all the cheese. The three holes close as the wedge collapses. Run about 100 steps to see the transformation, or recompile to restore the wedge. This is a stylized lattice melting model, not calibrated food physics; it omits latent heat, elasticity and surface tension.
No preview
Inside this simulation
Every named building block is public and reusable. These are the exact versions published with this simulation.
Source
// Golden Fondue: a heat-softened, conservative lattice material.
// Temperature, mass and time use illustrative lattice units, not calibrated cheese physics.
// Only the heater input supplies heat; the pan is a passive platform. No latent-heat,
// elastic-string, surface-tension or momentum solver; molten material creeps by local flux.
property FondueHeat { temperature: Float<0.05> [0.0 1.0]; }
property FondueMatter {
mass: Float<0.0> [0.0 1.0];
down: Float<0.0> [0.0 1.0];
left: Float<0.0> [0.0 1.0];
right: Float<0.0> [0.0 1.0];
front: Float<0.0> [0.0 1.0];
back: Float<0.0> [0.0 1.0];
}
// A static surface shade; the platform has neither heat nor cheese fields.
property FondueHardware { shade: Float<0.16> [0.0 1.0]; }
model Cheese : FondueHeat, FondueMatter { mass<1.0>; };
model FonduePan : FondueHardware;
model FondueSpace : FondueHeat, FondueMatter;
// Heater power: 0 switches off heating, 1 is full heat; intermediate values warm more gently.
in heater: Float<1.0> [0.0 1.0];
world { dimensions 3; cell Cube; neighborhood VonNeumann; }
// The input warms the material directly. Neighbor diffusion redistributes heat;
// a missing thermal neighbor (including the pan) is insulating.
behavior WarmCheese for FondueHeat
needs inputs(heater: Float [0.0 1.0])
{
update {
let exchange = sum n in neighbors[FondueHeat using VonNeumann] { n.temperature - self.temperature };
set self.temperature = clamp(self.temperature + 0.1 * exchange + 0.018 * (0.05 + 0.80 * heater - self.temperature), 0.0, 1.0);
}
}
// Cold cheese has a yield threshold. Above it, mobility increases continuously.
// The downward flux cannot exceed either donor mass or receiver capacity.
behavior PlanCheeseFall for FondueMatter + FondueHeat {
update {
let mobility = clamp((self.temperature - 0.25) / 0.4, 0.0, 1.0);
let below = (neighbors[FondueMatter using VonNeumann, -Z].mass else 1.0);
set self.down = min(self.mass, 1.0 - below) * mobility * 0.85;
}
}
behavior MoveCheeseDown for FondueMatter {
update {
let incoming = (neighbors[FondueMatter using VonNeumann, +Z].down else 0.0);
set self.mass = clamp(self.mass - self.down + incoming, 0.0, 1.0);
}
}
// Four horizontal fluxes share at most 80% of available mass/capacity. A supported
// layer spreads slowly; material in mid-air falls first instead of diffusing sideways.
behavior PlanCheeseSpread for FondueMatter + FondueHeat {
update {
let below = (neighbors[FondueMatter using VonNeumann, -Z].mass else 1.0);
let support = clamp((below - 0.6) / 0.4, 0.0, 1.0);
let mobility = clamp((self.temperature - 0.25) / 0.4, 0.0, 1.0) * support * 0.2;
set self.left = max(self.mass - (neighbors[FondueMatter using VonNeumann, -X].mass else self.mass), 0.0) * mobility;
set self.right = max(self.mass - (neighbors[FondueMatter using VonNeumann, +X].mass else self.mass), 0.0) * mobility;
set self.front = max(self.mass - (neighbors[FondueMatter using VonNeumann, -Y].mass else self.mass), 0.0) * mobility;
set self.back = max(self.mass - (neighbors[FondueMatter using VonNeumann, +Y].mass else self.mass), 0.0) * mobility;
}
}
behavior SpreadCheese for FondueMatter {
update {
let incoming = (neighbors[FondueMatter using VonNeumann, +X].left else 0.0)
+ (neighbors[FondueMatter using VonNeumann, -X].right else 0.0)
+ (neighbors[FondueMatter using VonNeumann, +Y].front else 0.0)
+ (neighbors[FondueMatter using VonNeumann, -Y].back else 0.0);
set self.mass = clamp(self.mass - self.left - self.right - self.front - self.back + incoming, 0.0, 1.0);
}
}
// Each plan is read from one completed phase, so every edge's loss has one equal gain.
phase heating { run WarmCheese on Cheese; run WarmCheese on FondueSpace; }
phase gravity_plan { run PlanCheeseFall on Cheese; run PlanCheeseFall on FondueSpace; }
phase gravity_move { run MoveCheeseDown on Cheese; run MoveCheeseDown on FondueSpace; }
phase spreading_plan { run PlanCheeseSpread on Cheese; run PlanCheeseSpread on FondueSpace; }
phase spreading_move { run SpreadCheese on Cheese; run SpreadCheese on FondueSpace; }
visual GoldenCheese for FondueMatter + FondueHeat {
color {
let soft = clamp((self.temperature - 0.25) / 0.4, 0.0, 1.0);
rgba(1.0, 0.78 - soft * 0.15, 0.16 - soft * 0.10, clamp(self.mass * 3.0, 0.0, 1.0))
}
}
visual PanSurface for FondueHardware {
color { rgba(self.shade, self.shade + 0.03, self.shade + 0.07, 1.0) }
}
visualize Cheese with GoldenCheese;
visualize FondueSpace with GoldenCheese;
visualize FonduePan with PanSurface;