Inspiration
The phrase "hurry up and wait" captures a common paradox: moving faster does not always mean arriving sooner. Traffic lights, intersections, and urban layouts force even the fastest travelers to stop and wait. Beyond the mechanical, life also isn't about speed first — there is a philosophical and emotional dimension to slowing down, patience, and presence. This scene plays that idea literally by pitting a courier against a snail through the same procedurally generated city.
What it does
"Hurry Up & Wait - Faster isn't always sooner" is a procedurally generated 3D animation built in Blender. It shows a courier and a snail traveling through the same city. Despite the courier's much higher speed, traffic signals and the structure of the city create stops that level the playing field. The animation makes the point visually: being faster does not guarantee arriving first.
How we built it
The scene is generated entirely through code, not manual Blender edits:
- Config-driven generation: All key numbers (timings, sizes, colors, seed) live in
config/scene.json.scripts/build_scene.pyreads this config to build the scene from scratch. - Deterministic procedural generation:
common.rng_for(cfg, salt)derives every random stream frommeta.seed, so the same config always produces the identical city. - Strict scene hygiene: Builders only remove objects/collections whose names start with
RJ_. The scene is namedHurryUpAndWait. Every object goes into a namedRJ_*collection (RJ_City,RJ_Road,RJ_Props,RJ_Courier,RJ_Snail,RJ_Lights,RJ_Cameras,RJ_FX). - Procedural materials only: No image textures or external asset files. All materials are generated procedurally.
- Portable packaging:
scripts/pack_renderjuice.pybuilds a self-contained package inrenderjuice/, andscripts/validate_portable.pyrenders test frames from the packaged.blend/.zipto verify portability. - Verification and QA:
scripts/qa_frames.pychecks specific frames and expected objects.scripts/benchmark.pymeasures render cost (important since Cycles runs on CPU).scripts/wip_renders.pyproduces labeled WIP stills. - Deterministic build workflow: Running
scripts/build_scene.pyregeneratesblender/hurry-up-and-wait.blendso the file always matchesconfig/scene.json.
Challenges we ran into
- CPU-only rendering: The environment runs Cycles on CPU (no GPU for EEVEE). Emissive materials/panels are expensive, so geometry and lighting had to be budgeted carefully.
benchmark.pyhelped track cost. - Procedural layout without manual fixes: Creating a coherent road network, intersections, and sensible routes procedurally while keeping it reproducible took careful parameterization.
- Determinism across changes: Keeping everything seeded via
rng_forwith salts meant any change had to preserve the "only touch what the salt covers" discipline. Small edits couldn't just be fudged in the.blend. - Portable validation: Ensuring the packaged scene renders correctly outside the original project tree (paths, linked data, etc.) required explicit validation with
validate_portable.py. - Staying config-driven: Avoiding hardcoded values in scripts was essential. Anything that affects timing/size/color belongs in
config/scene.json.
Accomplishments that we're proud of
- Fully reproducible build: One command (
scripts/build_scene.py) regenerates the entire scene deterministically from config. - Clean, disciplined generation: Strict
RJ_prefixing and non-destructive cleanup prevent accidental data loss. - Portable, contest-ready package: The build produces a validated portable zip that renders correctly in an isolated environment.
- Performance-aware design: Render cost is measurable and considered during changes because of CPU-only constraints.
- Clear verification workflow: QA checks are scripted (specific frames, expected objects, timing match). Claims about renders are backed by measured numbers.
What we learned
- Procedural generation rewards discipline: Config-driven + deterministic RNG makes iteration safer and debugging easier.
- Constraints drive better design: CPU-only Cycles forced prioritizing readability/performance over gratuitous geometry/glow.
- "Faster isn't always sooner" is a strong procedural premise: Signal timing and layout create narrative tension even with very simple actors (courier vs snail).
- Validation is part of the build: Portability/render checks catch real failures, not just warnings.
What's next for Hurry Up & Wait - Faster isn't always sooner
- Explore timing variations: Try different signal phasings/routes to show more clearly when/why the courier waits.
- More city variety: Generate different block sizes, intersection types, or lane counts while keeping determinism.
- Tighter storytelling: Highlight key moments (arrivals, waits) with framing/camera choices or clearer visual cues.
- Benchmark-driven polish: Find safe optimizations that reduce CPU render time without changing the core "hurry up and wait" behavior.
- Broader scenario set: Test other "faster isn't always sooner" cases while keeping the same config-driven, reproducible approach.
Built With
- 3d-animation
- algorithmic-city-generation
- asset-free-pipeline
- automated-testing
- benchmarking
- blender-4.5
- config-driven-pipeline
- cpu-rendering
- cycles
- deterministic-builds
- deterministic-rng
- json-configuration
- path-following
- portable-packaging
- portable-validation
- procedural-generation
- procedural-materials
- python
- qa-automation
- render-validation
- reproducible-builds
- scene-generation
- scripted-build
- timing-analysis
- traffic-simulation
Log in or sign up for Devpost to join the conversation.