Spaces:
Running on Zero
Running on Zero
kwabs22
Claude Opus 5.5
Add a pronoun rule option: most recent object or last clause's subject
6287002 |
Download SPEC.md from KwabsHug/VisualLearningIdea: direct link, hf CLI and curl.
- Browser
- Download file 13.6 kB
-
https://huggingface.co/spaces/KwabsHug/VisualLearningIdea/resolve/main/SPEC.md
- Command line
-
hf download hf://spaces/KwabsHug/VisualLearningIdea/SPEC.md
-
curl -L -o SPEC.md https://huggingface.co/spaces/KwabsHug/VisualLearningIdea/resolve/main/SPEC.md
13.6 kB
| # VisualLearningIdea: v1 spec | |
| Supersedes `visuallearning-handover.html`, which drifted over several HuggingChat | |
| sessions. `cat-sun-scene.html` stays as the UX reference for the serial reading. | |
| ## Idea | |
| 3D is a language. The Space **translates English into 3D**, the way you would | |
| translate English into Japanese. A picture is easier to take in than text, because | |
| the reader doesn't have to guess the exact picture. Once 3D exists it can be | |
| materialized (papercraft, origami, β¦). The **simplification ladder** shows how far | |
| a mesh can be reduced and still mean its word, and that is a main output. | |
| One Gradio Space serves people (UI) and models (`demo.launch(mcp_server=True)`). | |
| HuggingChat models can't run tools, which is why this Space exists. | |
| **The Space visualises; discussion happens in the chat** (e.g. HuggingChat over MCP). | |
| The calling model already sees the picture, so the Space runs no vision model of | |
| its own and stores no token. Because HTML is the final destination, every picture | |
| comes back with a **mini HTML page** the model can read, edit and build on. | |
| ## Translation terms | |
| | Translation | Here | | |
| |---|---| | |
| | Source β target language | English β 3D | | |
| | Dictionary | Ready assets: SimReady β Objaverse | | |
| | Grammar words | `the / a / and`: no geometry; a determiner opens a "which?" question | | |
| | Verbs | Relations between meshes | | |
| | Word alignment | `tokens[].commits`: reading order differs from build order | | |
| | Forced choices | `unresolved`: which asset, how big | | |
| | Back-translation | The chat model reading the picture: done in the discussion, not by the Space | | |
| ## Dictionary | |
| - Tiers, each taken **on rejection**: `simready β objaverse β primitive β generation`. | |
| - **Available** = a SimReady or Objaverse asset exists (no construction). No asset | |
| means not available, with no exceptions. The UI offers only available words, so | |
| the user knows before submitting. | |
| - A missing word can be used only by **explicitly choosing a primitive** for it | |
| (`box, sphere, cylinder, cone, plane`). In the UI the user picks one. Over MCP, | |
| `check_words` returns `missing` and the user's model (e.g. in HuggingChat) decides: | |
| rephrase, or pass `primitives_json` such as `{"sun": "sphere"}`. | |
| - Assets that **can't be used** are skipped and recorded in `decision.skipped`: | |
| no license, NC (non-commercial) or ND (no derivatives; simplification is a | |
| derivative), or no geometry. | |
| - SimReady: package list from `NVIDIA-Omniverse/simready-central/submissions/*`. | |
| Only geometry USD + thumbnail are downloaded, because textures are ~98% of each | |
| ~1 GB package. Assets are metric, so height comes from the asset. | |
| - Objaverse: LVIS categories (`lvis-annotations.json.gz`) are the word index; the | |
| license comes from `metadata/<shard>.json.gz`. No real scale, so height is a | |
| nominal default (an `unresolved` decision the user can set). | |
| - Light comes from the verb, not from a special word: whatever "shone" becomes the | |
| light source and sits in the sky along the light direction. | |
| - When nothing shines, the picture still needs light: seeing needs light as sitting | |
| needs a ground. The scene gets soft daylight from no object (`sun.daylight: true`, | |
| seeded direction, shadows on) and `unresolved` asks "what light?" (default: | |
| daylight; rephrase with "<noun> shone/glowed" to make an object the light). | |
| - SimReady geometry comes without textures, so the colour is the mean of the asset's | |
| 256 px thumbnail (backdrop and transparent pixels left out), stored as `item.color`. | |
| `item.thumbnail` is a public dataset URL, so the chat can show and compare assets. | |
| Uncoloured meshes render neutral light grey. | |
| - A "which <word>?" question carries `on_reject`: what rejecting the tier would offer | |
| (e.g. `{"tier": "objaverse", "candidates": 101, "offered": 9}`), since those | |
| alternatives are invisible until the tier is rejected. | |
| - The ground implied by "sat" (with no stated object) is a support plane created by | |
| the relation. It is not a typed word, so it needs no asset. To exist, a thing needs | |
| a reality to exist in: reading skips this, but visualising exposes it. So the | |
| implied ground is also surfaced in `unresolved` ("cat sat on what?", default: | |
| ground plane), for the discussion to accept or rephrase. | |
| ## Serial side vs configuration side | |
| - **Serial (ledger)**: tokens in reading order, items each word commits, relations, | |
| unresolved questions. **Freezing lives here.** Frozen items can't change; the | |
| others can be swapped, rejected, resized or rerolled. | |
| - **Configuration**: once the serial side is resolved, any placement satisfying the | |
| relations is valid. `seed` picks one (positions, yaw, sun direction). | |
| ### Ledger shape | |
| ```jsonc | |
| { | |
| "sentence": "the cat sat and the sun shone", | |
| "tokens": [{"index": 2, "text": "sat", "kind": "verb", "commits": ["ground", "r2"], "opens": []}], | |
| "items": [ | |
| {"id": "n1", "kind": "object", "word": "cat", "token": 1, "frozen": false, | |
| "decision": {"tier": "objaverse", "chosen": {β¦}, "alternatives": [β¦], | |
| "rejected_tiers": [], "skipped": [], "label": "cat"}, | |
| "height_m": {"value": 0.3, "source": "default"}, "license": "by"}, | |
| {"id": "ground", "kind": "environment", "role": "plane", "implied_by": 2}, | |
| {"id": "r2", "kind": "relation", "relation": "on", "subject": "n1", "object": "ground"}, | |
| {"id": "n5", "kind": "object", "word": "sun", "emits_light": true, | |
| "decision": {"tier": "primitive", "chosen": {"source": "primitive", "shape": "sphere"}, β¦}}, | |
| {"id": "r6", "kind": "relation", "relation": "lights", "subject": "n5", "object": "scene"} | |
| ], | |
| "unresolved": [{"id": "q0", "about": "n1", "question": "which cat?", "default_chosen": "cat", "alternatives": [β¦]}], | |
| "configuration": {"seed": 0, "placements": {"n1": {"position": [0, 0, 0], "yaw_deg": 12}}, "sun": {β¦}} | |
| } | |
| ``` | |
| Relations: `on, under, beside, near, behind, in_front_of, above, faces, lights`. | |
| Verbs: `sat/stood/lay/rested` β `on` (ground implied), `shone/glowed` β `lights` | |
| (shadows only turn on with this coupling), `faced` β `faces`. | |
| ## Paragraphs: sentences + bindings | |
| `extend_scene` adds the next sentence to an existing ledger (token and item ids carry | |
| on, `sentences` lists them). Within a sentence or across sentences: | |
| - **"the <noun>"** when that noun is already in the ledger refers to it (the most | |
| recent one) instead of adding an item. Surfaced as an implied question: "which cat? | |
| the one already mentioned" (default: the same cat), alternatives include "say | |
| 'another cat' for a new one". | |
| - **"a/an/another <noun>"** always adds a new item. | |
| - **Pronouns** (it, he, she, they, him, her, them) refer back by a rule, `pronouns` | |
| on `translate_to_scene`/`extend_scene`, kept in the ledger: | |
| - `"recent"` (default): the most recent object. | |
| - `"subject"`: the subject of the most recent clause, else the most recent object. | |
| Closer to how readers take "it": in "the cat sat on the table. the apple lay | |
| beside it", `recent` gives the table, `subject` the cat. | |
| Either way the clause's own subject is left out when the pronoun is its object ("the | |
| apple lay beside it" can't mean the apple), and the choice is surfaced as "'it' = the | |
| cat?" with the other objects as alternatives, most recent first. A pronoun with | |
| nothing before it is an error. | |
| - Each reference question carries `binds: {token, to}`; relations record which token | |
| named each end (`tokens: {subject, object}`), so a reference can be rebound. | |
| - **`bind`** (a `refine_scene` change): `{"id": "q10", "bind": "n1"}` rebinds a | |
| reference question; `{"id": "n12", "bind": "n1"}` merges a duplicate object into | |
| another (relations move, its "which?" question goes, a light carries over). Both | |
| refuse to relate an object to itself and add a note to `resolutions`. | |
| - "what light?" is asked once per ledger and dropped when a later sentence lights the scene. | |
| ## MCP tools | |
| | Tool | In β out | | |
| |---|---| | |
| | `check_words` | sentence, primitives β per-word status, missing words, completions | | |
| | `translate_to_scene` | sentence, primitives, seed, html, pronouns β ledger, PNG, GLB, mini HTML file, mini HTML text | | |
| | `refine_scene` | ledger, changes, seed, html β ledger, PNG, GLB, mini HTML file, mini HTML text | | |
| | `extend_scene` | ledger, sentence, primitives, seed, html, pronouns β ledger, PNG, GLB, mini HTML file, mini HTML text | | |
| | `simplify_item` | ledger, item id, html, rings, samples β report, rung PNGs, rung GLBs, mini HTML file, mini HTML text | | |
| | `papercraft_rings` | ledger, item id, rings, samples, height_cm β pieces report, PNG per A4 page (to look at), SVG per A4 page (mm, to print) | | |
| `check_words` also returns `maps_to` per noun (the asset it would get), so a word | |
| that exists but maps to the wrong thing shows up before submitting. | |
| Ladder rungs: original β 20k / 5k / 1k / 250 / 60 faces β convex hull β oriented | |
| box. Where the object stops reading as its word is judged in the discussion. | |
| Decimation welds split vertices (UV and normal seams) between passes. When quadric | |
| decimation still stalls (CAD meshes with sliver triangles, e.g. a SimReady lamp stuck | |
| at ~21k faces), vertex clustering takes over: cruder, but it always reduces and keeps | |
| thin parts. Rungs are | |
| labelled with the faces they actually have, and the ladder stops descending when | |
| a rung doesn't reduce, so a stalled mesh never shows as several identical rungs. | |
| **Rings rung (second path, for papercraft).** `rings` horizontal sections, each | |
| outline sampled at `samples` evenly spaced angles from the section's centre (the | |
| farthest crossing per ray), joined into bands with flat caps. Every band is one | |
| paper strip and every vertex is (ring, angle); the report lists each ring's height, | |
| centre and radii, so it can be checked by hand. One stack per object: each ray takes | |
| the farthest crossing, so disjoint outlines in one section merge (two legs become one | |
| band bridging the gap), branching or overhanging parts (a lamp's arm and shade) merge | |
| into one envelope, and heights with no section are skipped. | |
| ### Papercraft (rings) | |
| `papercraft_rings` turns the rings rung into a printable net (SVG, millimetres, real size | |
| or scaled to `height_cm`). Each band between two rings unrolls exactly into one strip: | |
| its triangles share edges in sequence, so laying them flat keeps every length. A band | |
| that would curl past ~120Β° on paper overlaps itself, so it is split into parts (2a, 2b). | |
| Every joint has glue tabs on one side only, labelled with the piece they meet (band top | |
| edge β next band or top cap, seam end β own start or next part, bottom cap rim β band 1). | |
| Caps under 4 mm are dropped and the band closes to a point; tabs under 2 mm are dropped. | |
| Cut solid lines, fold dashed lines. The report lists, per piece, `glues_to` (the tabs it | |
| carries) and `receives_from` (pieces whose tabs land on it), so a tabless piece such as | |
| the top cap still shows its joint. | |
| Output is A4 pages (190 Γ 277 mm inside 10 mm margins), each as a 150 dpi PNG (chats, | |
| `<img>` and fetchers show PNGs; SVG files come back without a MIME type) and as an SVG | |
| in millimetres for printing. Each piece is turned to its | |
| narrowest orientation and shelf-packed; strips longer than a page are split like curled | |
| ones. With `height_cm` blank the model is the tallest whose every piece fits a page, | |
| which is set by band width and cap size, not strip length. The report gives | |
| `real_height_cm`, `max_height_cm_for_a4` and `fits_a4`; a piece too big for A4 at a | |
| requested height gets a page of its own size. | |
| ### Mini HTML | |
| A self-contained page: Three.js (`three@0.160.0` from unpkg) plus one inline `D` | |
| data object holding the sentence, the reading (word β commits), relations, | |
| unresolved questions, sun, ground, and each object's position, yaw and colour. | |
| Each object also has either inline low-poly geometry (β€400 faces in a scene, | |
| rungs β€1000 faces in a ladder) or a primitive spec. Vertices are rounded to cm. | |
| Each inline mesh states `faces`, `budget` and `floor`: `floor: true` means the mesh | |
| wouldn't decimate down to the budget, so the page carries more than asked. | |
| The geometry is inline, so a saved page works after Gradio's temporary file URLs expire. | |
| **Geometry is for the handoff, not the loop.** Even at 400 faces a page costs | |
| thousands of tokens, which a multi-turn chat can't afford on every refine. So | |
| `html` defaults to `"url"`: the page comes back as a file URL and the text field is | |
| empty. Models iterate on the ledger and the picture, and pass `html="inline"` only | |
| when they build on the page (`"skip"` omits it). An expired URL costs nothing: the | |
| ledger fully determines the scene, and the next refine re-creates the page. | |
| **No secrets.** The Space is public and stores no token; the datasets are public | |
| and download without one. Optional env: `VLI_CACHE`. | |
| ## Known gaps / open questions | |
| - **Materialization**: only the rings path exists (`papercraft_rings`). Nets from the | |
| decimated rungs (general mesh unfolding) are not built yet. | |
| - **Generation tier** is a stub: rejecting a primitive returns a clear error before | |
| changing the ledger (use `shape` or `height_m` instead). | |
| - SimReady names β words is a heuristic: the name after its category is indexed | |
| as a whole phrase plus its final part (`interiorPart_sun_visor_a01` β "sun visor", | |
| "visor"; never "sun" alone). `maps_to` makes the remaining misses visible. | |
| - Objaverse "front" direction is unknown, so `faces` assumes +Z forward. | |
| - `on` uses the bounding-box top, so objects on irregular tops may float or sink. | |
| - Renders are matplotlib (headless, CPU): approximate shading and a flat shadow on | |
| the ground plane only. | |