GDD v0.1: full design doc, idea capture, MODELBEAST farm client + concept wave 1

Fall Guys x Splatoon x Micro Machines — paint-coverage-as-loadout blob racer.
GDD.md is the review artifact: feature tiers, draft 7-colour buff table,
tech recommendation (web-first three.js+Rapier), milestones, Opus 4.8 lanes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 18:15:56 +10:00
commit 1506c12759
9 changed files with 556 additions and 0 deletions

216
GDD.md Normal file
View File

@ -0,0 +1,216 @@
# BLOBBO — Game Design Document
**v0.1 — draft for John's review, 2026-07-17.**
Raw idea capture: [docs/IDEA.md](docs/IDEA.md). Everything below is proposed-for-review;
⚠️ marks decisions that need John's sign-off before Opus 4.8 execution lanes are written.
---
## 1. Pitch
**Fall Guys × Splatoon × Micro Machines.** Up to dozens of friendly white blobby
dudes race giant absurd Rube-Goldberg obstacle courses. Paint cannons along the
course spray colour — whatever sticks to your body is your loadout: **the relative
% coverage of each colour is your buff kit.** The course paints you; being painted
changes how you race the course.
**The invention nobody else has:** paint coverage measured *on your own body* as
the skill system, inside a physics-comedy racer where the course is one giant
player-triggered machine.
## 2. Pillars
1. **Comedy through physics** — soft wobbly blobs, out-of-scale props, telegraphed
chain reactions. Funny beats fair; fair beats random.
2. **Paint is everything** — identity, buffs, weight, size, strategy all read off
your painted body. No HUD-first design: your state is visible on you.
3. **Readable chaos** — every contraption telegraphs ~0.5s before it fires.
Getting flattened must feel earned/funny, never random (random = ragequit).
4. **One goo universe** — players are goo; the world fights back with other goo
(paint, honey, oil, mud, water, bubbles). Viscosity playground, not gimmick pile.
## 3. The Blobbo
Friendly white soft-body blob — Michelin-man silhouette, fewer rolls, big eyes.
Starts every race clean/white. Accumulates paint as a visible skin layer.
Soft-body wobble is cosmetic (shader/spring jiggle over a rigid physics proxy —
see §9); the comedy is in the wobble, the determinism is in the capsule.
## 4. Core loop
Race an obstacle course → paint cannons/zones spray colour → manage your coverage
mix (seek, avoid, cleanse, smudge) → coverage grants buffs that change traversal →
buffs + weight + size interact with the course machinery → finish placement →
next round / next course.
## 5. Feature list
Tiers: **[MVP]** first playable · **[V1]** launch · **[V2]** post-launch/stretch.
### 5.1 Paint system (the core)
- **[MVP]** 7 main colours; per-player texture-space coverage mask; live % per colour.
- **[MVP]** Buff activates at threshold (⚠️ proposal: ≥20% coverage), strength
scales with %; dominant colour drives visual/audio identity.
- **[V1]** **Secondary blends**: two adjacent colours both ≥20% → blend effect
replaces/joins the pair (see §6 table). Way more permutations, mastery =
deliberately building a mix.
- **[V1]** **Super-state**: ≥70% one colour = amped version of the buff **with a
drawback** — you glow, you're a target, one detergent bubble wrecks you.
- **[MVP]** **Cleansing**: detergent bubbles on course (full or partial cleanse ⚠️),
water rinses gradually.
- **[V1]** **Contact smudging**: bumping other blobs *transfers* paint both ways
(⚠️ transfer vs. plain removal — transfer is funnier and more strategic).
- **[V2]** Paint flaking: growing stretches/cracks paint off in pickup-able flakes.
### 5.2 Weight (paint = mass)
- **[MVP]** Coverage adds mass: heavy blobs sink pressure plates, tip see-saws,
wobble slower, launch shorter; clean blobs are light and flingable.
- **[MVP]** Detergent bubbles double as tactical weight-drops.
- **[V1]** Buoyancy inversion underwater: clean floats (fast surface lane),
painted sinks (slow bottom lane, better pickups).
### 5.3 Scale (environment scale as mechanic)
- **[V1]** Colour-driven size: ⚠️ purple = grow, pink = shrink (see §6). Big =
heavy/blocks/easy target; small = pipe shortcuts/hard to hit/flung far.
- **[V1]** Paint dilution: growing increases surface area → your % coverage drops.
Size trades directly against your buff loadout.
- **[V1]** Scale zones: shrink sections racing *inside* the machine (marble runs,
domino chains, gear guts); giant sections stomping over a model town while tiny
racers dodge your feet.
- **[V2]** Players as course furniture: a giant blob is a platform/counterweight
for tiny blobs — the machine incorporates the players.
### 5.4 Course-as-machine (Rube Goldberg)
- **[MVP]** Contraption toolkit: see-saws, boots, buckets, conveyors, dominoes,
gears, catapults, fans — **triggered by players, not timers**. Everyone upstream
rigs the course for everyone downstream.
- **[MVP]** Telegraph rule: every chain reaction winds up visibly ~0.5s before firing.
- **[V1]** Colour-keyed machinery: mechanisms respond only to blobs of their
colour (green valve needs a mostly-green blob → shortcut opens for them + any
tailgaters). Emergent alliances, zero UI.
- **[V1]** Persistent course state: triggered contraptions stay tripped/reset
slowly; sprayed paint lingers on surfaces as slip/grip zones. Back of the pack
races a messier course — chaos as natural rubber-banding.
- **[V2]** Gravity switches as contraption outputs: a lever flips gravity for a
zone; heavy blobs slam, light blobs drift. Ceiling-runs through upside-down rooms.
### 5.5 Physics zones & surfaces
- **[V1]** **Underwater**: buoyancy lanes + gradual paint rinse (shortcut costs loadout).
- **[V2]** **Zero-g**: momentum pinball; paint floats as wobbly globs mid-air —
fly through one to wear it.
- **[V1]** **Honey**: slow but sticky — climb walls/ceilings. Traversal tool.
- **[V1]** **Oil**: fast, no steering, luge; fire-buffed blob ignites a trail
behind them (emergent trap-laying).
- **[V1]** **Mud = anti-paint**: coats brown, masks colours, suppresses buffs until
washed — makes water double-edged.
- **[MVP]** Surface material framework (friction/stick/flow per material) so all
of the above are data, not code.
### 5.6 World & tone
- **[MVP]** Out-of-scale everyday settings (Micro Machines DNA): giant kitchen
tables, gardens, bathtubs, desks; comically huge and tiny props.
- **[V1]** Course sections themed as rooms of one absurd oversized house.
### 5.7 Format & multiplayer
- **[MVP]** Local-feel party size first: 8-player online rooms, single race.
- **[V1]** 3040 player fields, multi-round elimination show (Fall Guys format).
- **[V1]** **Server-authoritative simulation + anti-cheat from day one** — cheating
is what soured Fall Guys; non-negotiable design constraint, not a patch.
- **[V2]** Spectator/replay clips (the game is a clip generator — lean in).
## 6. Draft colour/buff table ⚠️ (the big review item)
7 mains on a wheel; adjacent pairs blend. All names/effects are proposals.
| Colour | Buff | Effect | Drawback / texture |
|---|---|---|---|
| **Red** | BURN | Speed-boost flame trail; ignites oil; melts wax/ice barriers | Slowly burns itself off (self-consuming coverage) |
| **Orange** | BOUNCE | Rubberized — higher bounces, contraptions launch you further | Harder to stop precisely |
| **Yellow** | ZAP | Trigger colour-keyed machinery at range; static bump-stun | Attracts to metal (yanked toward rails/magnets) |
| **Green** | GRIP | Sticky feet — immune to oil/ice slip, climb like honey, hold on through gravity flips | Slower top speed |
| **Blue** | SLICK | Waterproof (no rinse), swim jets, leaves a slippery trail | Your own trail is slippery to you too |
| **Purple** | MEGA | Grow big + heavy — plates, see-saws, shoving, blocking | Paint dilutes as you grow; huge target |
| **Pink** | MINI | Shrink small + light — pipe shortcuts, hard to hit, mega-flung | Can't trigger plates; gusts/fans own you |
Example blends (adjacent, both ≥20%) — final list needs a full pass:
- Red+Orange **METEOR** — flaming cannonball launches, smash light barriers.
- Orange+Yellow **PINBALL** — each bounce zaps nearby machinery.
- Yellow+Green **MAGNET** — stick to metal surfaces/rails, ride them.
- Green+Blue **SNAILGOD** — climbable slime trail others can also use.
- Blue+Purple **TSUNAMI** — heavy water cannonball; splash rinses nearby players.
- Pink+Red **ROCKET** — tiny + flaming = fastest projectile in the game.
## 7. Courses (initial set sketch)
1. **Breakfast Rush** [MVP greybox] — giant kitchen table; toaster launchers,
cereal avalanches, milk river (water zone), butter pat (oil).
2. **The Machine** [V1] — you shrink and race *through* the Rube Goldberg engine
that powers the house: marble runs, dominoes, gears.
3. **Bathtime** [V1] — bathtub ocean, soap-bubble cleansers everywhere, honey
(shampoo) walls, mud garden outside the window.
4. **Model Village** [V2] — grow-shrink alternating; giants stomp the village
while minis race through it.
## 8. Asset plan (MODELBEAST — local, free)
- **Concept wave (queued now)**: blobbo character sheet, painted variants, course
vistas, contraption props, paint cannon, bubble zone, logo explorations —
`flux_local` on the m3ultra farm.
- **Mesh wave (after concept review)**: hero props + blobbo base mesh via
image→GLB (`hunyuan3d_mlx` / `trellis_mac` / `sf3d`), Blender finish on ultra.
- Blobbo is a **bespoke simple mesh** (sphere-blob + blend shapes), not a
character_kit humanoid — the 2-base-mesh fleet rule doesn't apply, but clips/
rigs stay out of web builds per licensing zones (all generated = 🟢 web-ok).
- Paint itself is **procedural** (shader + splat decals), not generated art.
## 9. Tech stack ⚠️ (recommendation, needs sign-off)
**Recommend: web-first, three.js + Rapier (WASM), server-authoritative Node room
server, docker-deployed to the games VPS (partly.party) like the other games.**
- Fits the existing pipeline: GLB assets, browser distribution, docker deploys,
MRPGI/character-kit tooling knowledge.
- **Soft-body is faked**: rigid capsule/sphere physics proxy + cosmetic jiggle
(vertex springs/shader). Real soft-body × 40 players × netcode is not shippable;
fake wobble is indistinguishable in comedy terms.
- **Paint = Splatoon technique**: per-player UV-space mask texture, splats
rendered into it, GPU mip/reduction pass → live coverage %. Cheap, exact.
- Server runs headless Rapier as authority; clients interpolate. MVP at 8 players,
architecture sized for 40.
- **Decision gate**: if browser perf caps out at M1 milestone, port call to
Godot 4 (native) — flagged early, evaluated with data.
## 10. Milestones
- **M0 — Playground**: one greybox course, blob movement + jiggle, camera. Feels funny to walk.
- **M1 — Paint MVP**: splat pipeline, coverage %, 3 colours + buffs (Red/Green/Blue), bubbles, weight. *Decision gate on stack.*
- **M2 — The Machine**: contraption toolkit, telegraphs, surface materials, Breakfast Rush course.
- **M3 — Multiplayer**: 8-player rooms, server authority, smudging, race format.
- **M4 — The Show**: all 7 colours + blends, scale system, 2nd course, juice/VFX/audio, 40-player test.
## 11. Opus 4.8 execution lanes (preview — full instructions after this doc is approved)
- **Lane A — Engine/feel**: bootstrap, blob controller, camera, jiggle, greybox playground.
- **Lane B — Paint**: splat pipeline, coverage compute, buff/threshold/blend framework.
- **Lane C — Machine**: contraption toolkit, triggers/telegraphs, surface materials, gravity/zones.
- **Lane D — Netcode**: room server, authoritative sim, interpolation, anti-cheat posture.
- **Lane E — Art**: MODELBEAST asset flow → GLB integration, paint shader look-dev, VFX.
Lanes AC can start in parallel worktrees; D after A stabilizes the sim contract;
E continuous. Each lane gets its own instruction doc + acceptance criteria.
## 12. Risks
1. **Netcode × physics** — the classic killer. Mitigation: fake soft-body, authoritative server, small MVP player count.
2. **Readability under chaos** — mitigation: telegraph rule as a pillar, paint identity visible on bodies.
3. **Scope** — this doc is huge. Mitigation: tiers are real; MVP is one course, 3 colours, 8 players.
4. **Cheating** — server authority from day one; never trust client physics.
## 13. Open questions for review ⚠️
1. Buff table (§6) — right 7? right effects? purple/pink as the size pair?
2. Thresholds: 20% activate / 70% super — feel right?
3. Smudge = transfer (steal paint) or removal only?
4. Detergent bubbles: full cleanse or dominant-colour-only?
5. Tech stack sign-off (§9) — web-first OK?
6. Name check: BLOBBO vs. globbo (folder says globbo, repo says BLOBBO).

14
README.md Normal file
View File

@ -0,0 +1,14 @@
# BLOBBO
Fall Guys × Splatoon × Micro Machines: wobbly blob racers on giant Rube-Goldberg
courses where **the paint that lands on you is your loadout** — relative %
coverage per colour = your buffs, weight, and size.
- **[GDD.md](GDD.md)** — the design document (v0.1, in review). Start here.
- [docs/IDEA.md](docs/IDEA.md) — the original raw idea capture (2026-07-17).
- [tools/mb.py](tools/mb.py) — MODELBEAST farm client (concept art / mesh gen
on the local render farm). `tools/concept-wave-1.json` = first asset wave.
- `assets/concepts/` — generated concept art (review gate for the mesh wave).
Status: **pre-production.** GDD under review; next step is per-lane execution
instructions (see GDD §11) once features are signed off.

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

106
docs/IDEA.md Normal file
View File

@ -0,0 +1,106 @@
# GLOBBO — paint-buff blob racing
*Captured 2026-07-17. Raw idea, pre-design.*
## Elevator pitch
Fall Guys × Splatoon. Friendly white blobby dudes (Michelin-man-ish, fewer rolls)
race a giant absurd Rube-Goldberg obstacle course. Paint cannons along the course
spray colour; whatever sticks to you matters — **your relative % coverage of each
colour is your buff/skill loadout**. The course paints you, and being painted
changes how you play the course.
## Core mechanic — paint as loadout
- 7 primary colours, each mapped to a buff/effect (e.g. mostly-red = on fire /
speed burn, blue = ?, green = ?, etc.)
- Buffs scale with (or unlock at) **relative % of total coverage** — probably a
minimum threshold so trace splatter doesn't flicker buffs on/off.
- **Secondary blends**: adjacent colours mix (red+yellow zones on your body, or
overall ratio ≈ orange) → way more permutations than 7. Mastery = deliberately
building a blend, not just grabbing one colour.
- Course gives repeated chances at each colour → routing decisions: detour for
the paint you want vs. straight line.
## Counterplay / paint economy
- **Detergent bubbles** on course: cleanse (full or partial?) — reset a bad mix.
- **Bumping other players smudges paint off/onto each other** — contact matters,
griefers can wipe your carefully built loadout, or you can steal/share colour.
- Open Q: does smudging transfer colour (I take some of yours) or just remove?
Transfer is funnier and more strategic.
## Tone / physics
- Gravity-heavy, Rube-Goldberg-esque, out-of-scale props (comically huge and
tiny things) — absurd and funny, not sweaty.
- Blob physics: soft-body wobble is half the comedy (see Fall Guys, Gang Beasts,
Human Fall Flat).
## Course as machine (the extra Rube Goldberg twist)
- **Players are the marbles**: contraptions are triggered by blobs, not timers.
See-saw → boot → bucket → conveyor → paint dump on the tunnel below. Everyone
upstream is constantly (accidentally or deliberately) rigging the course for
everyone downstream.
- **Paint = weight**: coverage adds mass. Heavy blobs sink pressure plates and
tip see-saws but wobble slower / launch shorter. Merges the paint system and
the contraption system into one — route for colour *and* for mass, and
detergent bubbles become tactical weight-drops, not just buff-resets.
- **Colour-keyed machinery**: some mechanisms only respond to a blob of their
colour (mostly-green blob turns the green valve → shortcut opens for them and
anyone tailgating). Emergent temporary alliances, no UI.
- **The course remembers**: triggered contraptions stay tripped / reset slowly;
sprayed paint lingers on surfaces as slip/grip zones (Splatoon-style). The
back of the pack races a messier course than the leaders — chaos as natural
rubber-banding.
- **Readability rule**: every chain reaction telegraphs for ~0.5s (winding
gears, teetering buckets) so getting flattened reads as funny/earned, never
random. Random deaths are what make ragdoll racers rage-inducing.
## Scale play (environment scale as a mechanic, not just art)
- **Scale zones**: shrink sections where you race *inside* the machine (marble
runs, domino chains, between gears); giant sections where you stomp over a
model town and the tiny racers dodge your footsteps.
- **A colour controls size** (e.g. purple = grow, pink = shrink) — plugs into
weight/contraption systems: big = heavy, triggers plates, blocks paths, easy
paint target; small = light, pipe shortcuts, hard to hit, flung far.
- **Paint stretches with you**: growing dilutes coverage % (surface area up,
paint spread thin, maybe flakes off for others to grab); shrinking
concentrates it. Size trades directly against your buff loadout.
- **Players as course furniture**: a giant blob is a platform/obstacle/
counterweight for tiny blobs — the machine incorporates the players.
- Prior art: Superliminal (perspective scale tricks), Katamari (scale
escalation), Grounded (tiny-scale traversal).
## Physics zones & surfaces (everything is goo/viscosity)
- **Gravity switches**: contraption outputs, not fixed — a lever flips gravity
for a zone. Heavy painted blobs slam to the new floor, light clean blobs
drift. Ceiling-running through upside-down oversized rooms.
- **Underwater**: buoyancy = inverse weight. Clean blobs float across the top
(fast lane), paint-heavy blobs sink (slow lane, better pickups?). Water
slowly rinses paint — underwater shortcuts cost loadout.
- **Zero-g / space**: momentum pinball; paint floats as wobbly globs in the
air — fly through one to wear it. Mid-air coating unique to zero-g.
- **Honey**: slow but sticky = climb walls/ceilings. Traversal tool.
- **Oil**: fast, no steering, luge. Fire-buff blob crossing oil ignites a
trail behind them — emergent trap-laying.
- **Mud = anti-paint**: coats you brown, masks colours, suppresses buffs until
washed off. The debuff you wear; makes water sections double-edged.
- Theme glue: players are goo, environment fights back with other goo. One
coherent viscosity playground, not a gimmick grab-bag.
## Open questions
- Buff table for the 7 primaries + which blends do what.
- Threshold model: min % to activate? dominant-colour-only? top-2 blend?
- Is 100% one colour a super-state ("all red = on fire") with a downside
(visible, targetable, one bubble ruins you)?
- Player count / real-time netcode (Fall Guys did 60; soft-body + paint sim +
50 players online is the hard engineering problem).
- Does paint also affect the *course* (Splatoon-style ink on surfaces = grip /
slip zones) or only the player?
- Anti-cheat from day one — cheating is literally what soured Fall Guys.
## Prior art
- **Fall Guys** — the racing/format template (the game John couldn't name).
- **Splatoon** — paint identity, coverage %, but coverage is territory not self.
- **Micro Machines / Re-Volt / Toy Commander** — out-of-scale courses: tiny
racers in giant everyday environments (breakfast tables, supermarkets, a
whole house). The scale-absurdity tone reference.
- (Not these but adjacent: Army Men, Toy Soldiers — toy-scale combat, not racing.)
- Key novelty: coverage is measured **on your own body** and is your skill kit.

Binary file not shown.

34
tools/concept-wave-1.json Normal file
View File

@ -0,0 +1,34 @@
[
{
"name": "blobbo-character-sheet",
"prompt": "Character concept turnaround sheet for a friendly white blob mascot creature, soft rounded body like a simplified michelin man with subtle segments, big cute glossy eyes, tiny smile, stubby arms and feet, front view and side view, clean light studio background, modern 3D game character concept art, playful stylized look"
},
{
"name": "blobbo-painted-fire",
"prompt": "Friendly white blob mascot creature mid-run in a racing pose, splattered head to toe in bright red and orange paint that glows like embers, drips and splats, leaving small flames behind, energetic game concept art, vibrant colors, light background"
},
{
"name": "course-breakfast-rush",
"prompt": "Game environment concept art, wide shot: an obstacle course race across a giant breakfast table from a tiny creature's perspective, absurd rube goldberg machines, toaster catapult launching toast, cereal box avalanche, milk river, sugar cube stepping stones, ramps and see-saws made of cutlery, bright playful saturated colors"
},
{
"name": "prop-paint-cannon",
"prompt": "Game prop concept art on white background: a chunky oversized cartoon paint cannon loaded with bright yellow paint, friendly toy-like design, mounted on a wooden rube goldberg contraption with big gears and a big red lever, paint drips around the muzzle"
},
{
"name": "prop-bubble-cleanser",
"prompt": "Game environment prop concept: a giant bubble wand archway over a race track blowing iridescent soap bubbles and foam, sparkling clean zone, playful cartoon style, bright lighting, concept art"
},
{
"name": "prop-contraption-sheet",
"prompt": "Concept art sheet of rube goldberg contraptions for a physics racing game, white background: giant boot on a spring kicking a paint bucket, wooden see-saw, conveyor belt, giant dominoes, huge interlocking gears, catapult, chunky friendly toy-like designs, bright colors"
},
{
"name": "logo-blobbo",
"prompt": "Video game logo, the word BLOBBO written in fat glossy white blob letters dripping with rainbow paint splatters, bold and playful, centered on a clean white background, vibrant, professional game branding"
},
{
"name": "course-model-village",
"prompt": "Comedic game concept art: a giant friendly white blob creature covered in purple paint stomping carefully through a tiny model village racetrack while tiny blob creatures race between its feet, dramatic scale difference, bright cheerful colors, wide shot"
}
]

186
tools/mb.py Normal file
View File

@ -0,0 +1,186 @@
#!/usr/bin/env python3
"""mb.py — BLOBBO client for the MODELBEAST farm (m3ultra central queue :8777).
Contract per ~/Documents/MESHGOD/scripts/mb_recon.py (verified 2026-07-16):
Bearer MB_TOKEN (env, else ~/Documents/backnforth/.env), POST /api/jobs,
poll GET /api/jobs/{id}, retrieve own assets by parent_job (never newest-glob
concurrent jobs race). Job JSON may carry raw control chars strip pre-parse.
Guest tier: max 4 active jobs, enforced here with a submit window.
Usage:
mb.py gen "<prompt>" out.png one flux_local image
mb.py batch prompts.json outdir/ [{"name":..,"prompt":..,"operator"?,"params"?}]
mb.py img2glb in.png out.glb [operator] image -> mesh (default hunyuan3d_mlx)
mb.py jobs list my recent jobs
"""
import json, mimetypes, os, sys, time, urllib.request, urllib.error, uuid
HOST = os.environ.get('MB_HOST', 'http://100.89.131.57:8777')
MAX_ACTIVE = 4
def token():
t = os.environ.get('MB_TOKEN')
if t:
return t
for line in open(os.path.expanduser('~/Documents/backnforth/.env')):
if line.startswith('MB_TOKEN='):
return line.split('=', 1)[1].strip()
sys.exit('no MB_TOKEN')
def req(path, data=None, headers=None, raw=False):
h = {'Authorization': f'Bearer {token()}'}
h.update(headers or {})
r = urllib.request.Request(HOST + path, data=data, headers=h)
body = urllib.request.urlopen(r, timeout=180).read()
if raw:
return body
s = body.decode('utf-8', 'replace')
return json.loads(''.join(c if c >= ' ' or c in '\t' else ' ' for c in s))
def submit(operator, params=None, asset_id=None):
"""Returns job id, or None on 429 (farm throttles submits harder than the
4-active contract observed 429 on the 4th rapid submit, 2026-07-17)."""
payload = {'operator': operator, 'params': params or {}}
if asset_id:
payload['asset_id'] = asset_id
try:
j = req('/api/jobs', data=json.dumps(payload).encode(),
headers={'Content-Type': 'application/json'})
except urllib.error.HTTPError as e:
if e.code == 429:
return None
raise
return j['id']
def status(jid):
return req(f'/api/jobs/{jid}').get('status')
def upload(path):
boundary = uuid.uuid4().hex
ctype = mimetypes.guess_type(path)[0] or 'application/octet-stream'
name = os.path.basename(path)
body = (f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="{name}"\r\n'
f'Content-Type: {ctype}\r\n\r\n').encode() + open(path, 'rb').read() + f'\r\n--{boundary}--\r\n'.encode()
a = req('/api/assets', data=body, headers={'Content-Type': f'multipart/form-data; boundary={boundary}'})
return a.get('id') or (a.get('items') or [a])[0].get('id')
def outputs_for(jid, suffix=None):
a = req('/api/assets?limit=200')
items = a if isinstance(a, list) else a.get('items', [])
mine = [x for x in items if x.get('parent_job') == jid]
if suffix:
mine = [x for x in mine if str(x.get('filename', x.get('name', ''))).endswith(suffix)]
return mine
def fetch(asset_id, out):
data = req(f'/api/assets/{asset_id}/file', raw=True)
open(out, 'wb').write(data)
return len(data)
def wait(jid, label='', interval=6):
t0 = time.time()
while True:
st = status(jid)
if st in ('done', 'error', 'cancelled'):
print(f'[mb] {label or jid}: {st} ({int(time.time()-t0)}s)', flush=True)
return st
time.sleep(interval)
def cmd_gen(prompt, out):
jid = submit('flux_local', {'prompt': prompt})
if wait(jid, out) != 'done':
sys.exit(1)
outs = outputs_for(jid)
if not outs:
sys.exit('[mb] no output asset for job')
n = fetch(outs[0]['id'], out)
print(f'[mb] wrote {out} ({n//1024}KB)')
def cmd_batch(spec_path, outdir):
jobs = json.load(open(spec_path))
os.makedirs(outdir, exist_ok=True)
done_names = {os.path.splitext(f)[0] for f in os.listdir(outdir)}
jobs = [j for j in jobs if j['name'] not in done_names]
if done_names:
print(f'[mb] resume: skipping {len(done_names)} already in {outdir}', flush=True)
pending, active, results = list(jobs), {}, {}
while pending or active:
while pending and len(active) < MAX_ACTIVE:
job = pending[0]
params = dict(job.get('params', {}))
if 'prompt' in job:
params.setdefault('prompt', job['prompt'])
jid = submit(job.get('operator', 'flux_local'), params)
if jid is None:
print('[mb] throttled (429), backing off 15s', flush=True)
time.sleep(15)
break
pending.pop(0)
active[jid] = job
print(f'[mb] submitted {job["name"]} -> {jid}', flush=True)
time.sleep(6)
for jid in list(active):
st = status(jid)
if st in ('done', 'error', 'cancelled'):
job = active.pop(jid)
results[job['name']] = st
if st == 'done':
outs = outputs_for(jid)
if outs:
name = str(outs[0].get('filename', outs[0].get('name', 'out')))
ext = os.path.splitext(name)[1] or '.png'
out = os.path.join(outdir, job['name'] + ext)
n = fetch(outs[0]['id'], out)
print(f'[mb] {job["name"]}: done -> {out} ({n//1024}KB)', flush=True)
else:
print(f'[mb] {job["name"]}: done but no asset visible', flush=True)
results[job['name']] = 'done-no-asset'
else:
print(f'[mb] {job["name"]}: {st}', flush=True)
bad = {k: v for k, v in results.items() if v != 'done'}
print(f'[mb] batch complete: {len(results)-len(bad)}/{len(results)} ok'
+ (f' — failed: {bad}' if bad else ''))
def cmd_img2glb(img, out, operator='hunyuan3d_mlx'):
aid = upload(img)
print(f'[mb] uploaded {os.path.basename(img)} -> asset {aid}')
jid = submit(operator, {}, asset_id=aid)
if wait(jid, out, interval=10) != 'done':
sys.exit(1)
outs = outputs_for(jid, '.glb')
if not outs:
sys.exit('[mb] no glb for job')
n = fetch(outs[0]['id'], out)
print(f'[mb] wrote {out} ({n//1024}KB)')
def cmd_jobs():
j = req('/api/jobs?limit=20')
items = j if isinstance(j, list) else j.get('items', [])
for x in items:
print(x.get('id'), x.get('operator'), x.get('status'))
if __name__ == '__main__':
cmd = sys.argv[1] if len(sys.argv) > 1 else ''
if cmd == 'gen':
cmd_gen(sys.argv[2], sys.argv[3])
elif cmd == 'batch':
cmd_batch(sys.argv[2], sys.argv[3])
elif cmd == 'img2glb':
cmd_img2glb(*sys.argv[2:5])
elif cmd == 'jobs':
cmd_jobs()
else:
sys.exit(__doc__)