From 2bce0c03fc9468ac6bd990df58e4af102d883123 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 28 Sep 2026 18:41:36 -0700 Subject: [PATCH] flat figures written down; objects compile when first asked for MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OBJECT-DIALECT.md: flat figures as built — the joints read off the live pose, the figure dialect (bone, skin, on, surface, palette inks, switches), the trio face on the head's sphere, the wardrobe, FIGURE and LOOK, the numbers, and what isn't done. OSKIEWAR-HOSTS.md §7 decision 0: option (c) being tried. gameObjects compiles (and bakes) an object the first time the game asks for it rather than at load, so the figure costs nothing while its flag is off. --- xbox/OBJECT-DIALECT.md | 87 ++++++++++++++++++++++++++++++++++++ xbox/OSKIEWAR-HOSTS.md | 10 +++++ xbox/live/oskiewar.js | 11 +++-- xbox/tools/embed-objects.mjs | 13 ++++-- 4 files changed, 114 insertions(+), 7 deletions(-) diff --git a/xbox/OBJECT-DIALECT.md b/xbox/OBJECT-DIALECT.md index f6a9d41c33..61f9c2d742 100644 --- a/xbox/OBJECT-DIALECT.md +++ b/xbox/OBJECT-DIALECT.md @@ -278,6 +278,93 @@ figures, and it suits them better than (b): 40–50 numbers, against today's ~1700 screen-space ops for all figures. - **Face and hair:** flat plates and strokes anchored to the head. +### Flat figures, built (2026-09-28, behind a flag) + +`objects/figure-flat.lisp` is option (c). The game draws it in place of the +rig when `?flat-figures` is in the page's address or +`globalThis.oskiewarFlatFigures = true`. It is off by default. Gameplay, +hitboxes and the sim are untouched; this is drawing only. + +- **The pose is the input.** The game reads 16 joints off the pose the rig + already built (`runnerWorldGeometry`), every frame: + - the head's centre, and where it looks; + - neck and pelvis; + - shoulders, elbows and hands; + - hips, knees and feet. + + Joints are found by the limb and the bone's kind, not the full role, + because a kick renames the legs `lead-`/`rear-thigh`. A joint the body lacks + is NaN, and what hangs on it isn't drawn. A frame-conformance test holds + the joints to the rig's bone ends during a punch and a kick. +- **Where the head looks.** In the pool it's the skater's own heading. In an + arena it's toward the lens, turned .42 rad to the facing side, as + `drawTrioFace` turns. Both are lifted to the lens's height, so the face + sits on the head the way the drawn face does. +- **The dialect for figures:** + - `(bone a b r)` is a stadium between two joints; an arm bends at the elbow + for free. + - `(skin a b (c x y z) …)` is a flat polygon through joints, optionally + offset. + - `(on joint …)` hangs shapes on a joint. On `head` it uses the head's frame + (x right, y up, z where it looks) in head radii. On a body joint it uses + the body's frame (up the spine, facing as the head does, x from the right + hip to the left) in world units. + - `(surface …)` makes shapes one-sided along the local z, so a face passes + out of sight round the back. + - Palette names in `ink` (`skin hair shirt pants skirt shoe accent iris lip + blush`) are slots a player's LOOK fills, so one baked sketch dresses + everyone. + - The switches `blink`, `hurt` (X eyes), `skirt` and `glasses` choose the + variant. +- **The face** is `drawTrioFace` moved onto the head's sphere, feature by + feature, at the same longitudes and latitudes: tall eyes with an ink rim, a + blue iris, a pupil and catchlight, the lash line and three lashes, tilted + brows, blush, a small nose, pink lips lifted at one corner, and glasses. + It blinks, and shows X eyes when hurt. +- **The wardrobe:** + - shirt and sleeves; + - bare forearms and hands; + - trousers, or a skirt that flares from the hips past the knees; + - shoes; + - a hair cap with locks past the temples and a top-knot with its tail; + - the chest's heart and daisy. +- **The ops.** + - **FIGURE** (op 20) is sketch · look · pin · 16 joints: **52 numbers per + fighter per tick**. + - **LOOK** (op 21) is a player's 10-colour palette, sent once. + - The host only projects each anchor (joint plus offset in that joint's + frame), culls one-sided shapes, and fills with ink edges. It never + tessellates a form. + - `pin` stands an arena fighter at one depth, the floor's near edge, as the + rig does, keeping a tenth of each shape's depth so limbs still layer. + - Hosts without `frame` (the console before R6, the harness) draw the same + shapes through `drawFigureShapes`, embedded from `object-lisp.mjs`. +- **Measured, per fighter, through the game's own frame program** + (frame-conformance): + + | lens distance | today: numbers | today: host triangles | flat: numbers | flat: host triangles | + |---|---|---|---|---| + | 260 | 1954 | 586 | 52 | 476 | + | 700 | 1954 | 474 | 52 | 306 | + | 1500 | 1330 | 322 | 52 | 264 | + + Stadiums and ellipses take the 2 px chord rule. The legs aren't drawn under + a skirt. + +**Not yet:** +- The bow, the emo fringe and scars. +- Hit-segment flashes. +- `drawSpinArms`' two-tone sleeves. +- The loose runner's bowed limbs: flat limbs are straight between joints, so + a flung arm reads straighter. +- The sparring target. +- Dummies' faces and civilians' simpler faces: everyone gets the trio face. +- Ragdoll and broken-body states. Ragdolls draw flat through the fallen pose; + a burst head keeps the rig's drawing. +- A first-person inset check with the flag on. +- The console: `FrameVm.cpp` needs FIGURE and LOOK in R6. Until then it takes + the immediate path. + ## In the game Since 2026-09-28 the game draws the monowheel as `objects/monowheel-flat.lisp`. diff --git a/xbox/OSKIEWAR-HOSTS.md b/xbox/OSKIEWAR-HOSTS.md index 81fd41b419..829f4c9833 100644 --- a/xbox/OSKIEWAR-HOSTS.md +++ b/xbox/OSKIEWAR-HOSTS.md @@ -303,6 +303,16 @@ nothing waits on the console. primitives (world capsules and discs, the face as a textured billboard) that every interpreter projects, so no painter is duplicated. (b) is the agnostic one and the larger rewrite. + **(c), being tried (2026-09-28):** figures as world-anchored flat shapes: + bones between the pose's joints, the trio face on the head's sphere, flat + fills, ink edges. + - **What it avoids:** it duplicates no painter, as (a) would, and bends + for free, unlike (b). + - **Cost:** one FIGURE op (52 numbers) per fighter, against ~2000 today, + and fewer host triangles at every distance. + - **Where it is:** in the game behind `?flat-figures` / + `globalThis.oskiewarFlatFigures`, off by default. + - `xbox/OBJECT-DIALECT.md` → Figures has the design and what isn't done. 1. Keep Canvas2D as a fallback, or make WebGL2 the only web path? 2. ~~Photo materials on the web or flat as the reference?~~ Flat is the reference (2026-09-26). diff --git a/xbox/live/oskiewar.js b/xbox/live/oskiewar.js index 05260f2b07..ceaf7d2abe 100644 --- a/xbox/live/oskiewar.js +++ b/xbox/live/oskiewar.js @@ -26584,9 +26584,14 @@ const gameObjects = (() => { monowheel: "; monowheel-flat — the freeskate onewheel, drawn flat: world-anchored 2D shapes\n; with ink outlines, no lighting. The same rig as monowheel.lisp: x forward,\n; y up, z to the rider's right, the axle at the origin, a 24 radius tire.\n; Three parts bake — the shadow, the rolling wheel, the leaning deck — so a\n; tick is three SKETCH ops, and the host projects and fills the shapes.\n; Only the silhouettes (tire, deck) carry ink: the details sit on fills that\n; already contrast, and every outline doubles what the host draws.\n\ndef r 24\ndef half 13\ndef ink-edge 1.4\n\n(let roll (/ distance r))\n(let squash (* .14 (max 0 (- 1 (* land 5)))))\n(let rattle (* 2.5 (max 0 (- 1 (* hit 4))) (sin (* time 70))))\n\n; the shadow stays flat on the ground, behind everything: 103 units back is\n; the depth the game gives its own spot shadows (caster + .018), so it sits\n; on the floor the same way theirs do, in about their grey\n(nudge 103\n (ink 54 58 66)\n (move 0 (- r) 0 (scale 2.7 1 .8 (ring y 22))))\n\n(move 0 (- r) rattle\n (rotate x lean\n (scale (+ 1 squash) (- 1 squash) 1\n (move 0 r 0\n ; the wheel rolls as one: the tire (round, so turning it changes\n ; nothing), then the face you can see — rim, web, spokes, hub\n (rotate z (- roll)\n (ink 40 36 48)\n (outline ink-edge 20 16 28 (drum z r (* half 2)))\n (toward z\n (move 0 0 half\n (ink (mix 214 236 turbo) (mix 216 190 turbo) (mix 226 255 turbo))\n (ring z 17)\n (ink 58 52 66)\n (move 0 0 .2 (ring z 13.5))\n (ink (mix 232 190 turbo) (mix 72 90 turbo) (mix 130 255 turbo))\n ; spokes as flat bars: two triangles each where a round end costs eight\n (plate -13 -1.4 .4 13 -1.4 .4 13 1.4 .4 -13 1.4 .4)\n (plate -1.4 -13 .4 1.4 -13 .4 1.4 13 .4 -1.4 13 .4)\n (ink 70 64 80)\n (ball 0 0 .6 3.5))))\n ; one deck, fore to aft, the tire poking through it; lamps at the\n ; ends, white ahead and red behind\n (nudge 14\n (ink (mix 44 102 turbo) (mix 42 35 turbo) (mix 54 163 turbo))\n (outline ink-edge 20 16 28 (slab -60 2 -19 60 8 19)))\n (ink 240 244 250)\n (ball 62 5 0 3.2)\n (ink 240 70 70)\n (ball -62 5 0 3.2)))))\n", figure: "; figure-flat — an oskiewar fighter drawn flat: shapes hung on the pose's\n; joints, which the game hands over every tick (a FIGURE op), inked on the\n; silhouette. Colours are palette slots a player's LOOK fills, so one baked\n; sketch dresses everyone. Limbs are bones between joints, so an arm bends at\n; the elbow for free. The face is the trio face (drawTrioFace in oskiewar.js):\n; each feature sits on the head's sphere at the same longitude and latitude,\n; in its own tangent frame (x right, y up, z out; head radii), one-sided, so it\n; turns with the head and passes out of sight round the back.\n; Switches: blink, hurt (X eyes), skirt, glasses.\n\ndef edge 1.3\ndef face-edge .075\n\n; the body, back to front as depth sorts it: legs, torso, arms, head\n(outline edge 20 16 28\n ; legs: thighs unless a skirt hides them; shins show below its hem\n (ink pants)\n (if (not skirt)\n (bone hip-l knee-l 5.6)\n (bone hip-r knee-r 5.6))\n (bone knee-l foot-l 5)\n (bone knee-r foot-r 5)\n (if skirt\n ; a skirt: from the hips, flaring to a hem past the knees (x runs to her\n ; left hip, so +x is out on the left)\n (ink skirt)\n (skin (hip-l 4 4 0) (hip-r -4 4 0) (knee-r -20 -16 0) (knee-l 20 -16 0)))\n (ink shoe)\n (on foot-l (ball 0 0 0 5.4))\n (on foot-r (ball 0 0 0 5.4))\n (ink shirt)\n (bone neck pelvis 13.5)\n (bone shoulder-l shoulder-r 7)\n (bone shoulder-l elbow-l 4.6)\n (bone shoulder-r elbow-r 4.6)\n (ink skin)\n (bone elbow-l hand-l 4)\n (bone elbow-r hand-r 4)\n (on hand-l (ball 0 0 0 4.6))\n (on hand-r (ball 0 0 0 4.6))\n (bone neck head 4))\n\n; the chest's decals, flat on the shirt's front (the neck joint's frame:\n; x right, y up the spine, z forward): a heart and a daisy\n(on neck\n (surface\n (ink 255 70 120)\n (ball -7.5 -12 14.5 2.6)\n (ball -3.5 -12 14.5 2.6)\n (plate -10 -12.8 14.5 -1 -12.8 14.5 -5.5 -18 14.5)\n (ink 255 255 255)\n (repeat 5 k\n (move 5 -19 14.6 (rotate z (* k (/ tau 5)) (ball 2 0 0 1.6))))\n (ink 250 205 60)\n (ball 5 -19 14.8 1.3)))\n\n; the head, inked in head radii like everything hung on it\n(on head\n (outline .06 20 16 28\n ; hair behind the head, so the face shows in front and a cap round it,\n ; locks falling past the temples, a knot on top with its tail\n (ink hair)\n (ball 0 .12 -.2 1.04)\n (stroke .34 -.8 .4 -.25 -.86 -.3 -.3)\n (stroke .34 .8 .4 -.25 .86 -.3 -.3)\n (ball 0 .95 -.35 .3)\n (stroke .2 0 1.05 -.4 .08 1.42 -.62)\n (ink skin)\n (ball 0 0 0 1)))\n\n(on head\n ; the fringe, a cap of hair over the brow\n (ink hair)\n (surface (rotate x -1.02 (move 0 0 .96 (scale 1 .5 1 (ring z .82)))))\n ; blush\n (ink blush)\n (repeat 2 side\n (rotate y (* (- (* side 2) 1) .56) (rotate x .37 (move 0 0 .97\n (surface (scale 1 .55 1 (ring z .2)))))))\n ; eyes: white with an ink rim, iris, pupil, catchlights; lash line and lashes;\n ; the brow at a curious tilt. Blinking, a lid; hurt, an X.\n (repeat 2 side\n (let s (- (* side 2) 1))\n (rotate y (* s .3675) (rotate x (- .0875 (* s .0245)) (move 0 0 .97\n (surface\n (if (and (not blink) (not hurt))\n (if glasses\n ; a round frame: a dark ring, the lens in skin inside it\n (ink 24 18 26)\n (move 0 0 -.03 (scale 1 1.12 1 (ring z .3)))\n (ink skin)\n (move 0 0 -.02 (scale 1 1.12 1 (ring z .255))))\n (outline face-edge 24 18 26\n (ink 248 248 250)\n (scale 1 1.67 1 (ring z .162)))\n (ink iris)\n (move 0 0 .02 (ring z .082))\n (ink 6 6 10)\n (move 0 0 .03 (ring z .05))\n (ink 255 255 255)\n (move -.02 .035 .04 (ring z .023))\n (ink 24 18 26)\n (stroke .12 -.17 .09 .05 -.08 .25 .05 .08 .25 .05 .17 .09 .05)\n (stroke .07 (* s .15) .2 .05 (* s .27) .33 .05)\n (stroke .06 (* s .08) .25 .05 (* s .17) .38 .05))\n (if blink\n (ink 24 18 26)\n (stroke .08 -.16 0 .02 -.07 -.06 .02 .07 -.06 .02 .16 0 .02))\n (if hurt\n (ink 24 18 26)\n (stroke .09 -.13 .13 .02 .13 -.13 .02)\n (stroke .09 -.13 -.13 .02 .13 .13 .02))\n (ink 24 18 26)\n (stroke .09 -.15 (+ .37 (* s .02)) .02 -.02 .43 .02 .15 (+ .36 (* s .01)) .02))))))\n ; glasses' bridge\n (if glasses\n (ink 24 18 26)\n (rotate x .09 (move 0 0 .99 (surface (stroke .05 -.1 0 0 .1 0 0)))))\n ; nose and mouth: a small check, pink lips with the far corner lifted\n (ink 24 18 26)\n (rotate x .33 (move 0 0 .98 (surface (stroke .045 .005 .04 .01 -.025 -.02 .01 .02 -.02 .01))))\n (rotate x .57 (move 0 0 .95 (surface\n (ink lip)\n (scale 1 .38 1 (ring z .24))\n (ink 24 18 26)\n (stroke .055 -.24 .01 .02 0 -.02 .02 .24 .05 .02)))))\n", }; - const compiled = {}; - for (const name in sources) compiled[name] = objectLisp.compile(sources[name], name); - return { ...compiled, light: objectLisp.objectLight, drawFigureShapes: objectLisp.drawFigureShapes }; + // Each compiled (and baked) the first time it is asked for, not at load: + // an object the game never draws costs nothing. + const objects = { light: objectLisp.objectLight, drawFigureShapes: objectLisp.drawFigureShapes }; + for (const name in sources) { + let compiled = null; + Object.defineProperty(objects, name, { get: () => compiled || (compiled = objectLisp.compile(sources[name], name)) }); + } + return objects; })(); // function drawMonowheel(p){ diff --git a/xbox/tools/embed-objects.mjs b/xbox/tools/embed-objects.mjs index 013f5ea7d5..fedcb8b6bb 100644 --- a/xbox/tools/embed-objects.mjs +++ b/xbox/tools/embed-objects.mjs @@ -6,7 +6,7 @@ // import the compiler the object lab uses. This writes it into a sealed block // between markers, in its own scope, with each object's source as a string, // and exposes one global, `gameObjects`: each object compiled — and so baked -// — once, when the script loads. The lab and the game run the same compiler +// — once, the first time the game asks for it. The lab and the game run the same compiler // on the same source. // // node xbox/tools/embed-objects.mjs rewrite the block @@ -56,9 +56,14 @@ export function generate() { " const sources = {", ...sources, " };", - " const compiled = {};", - " for (const name in sources) compiled[name] = objectLisp.compile(sources[name], name);", - " return { ...compiled, light: objectLisp.objectLight, drawFigureShapes: objectLisp.drawFigureShapes };", + " // Each compiled (and baked) the first time it is asked for, not at load:", + " // an object the game never draws costs nothing.", + " const objects = { light: objectLisp.objectLight, drawFigureShapes: objectLisp.drawFigureShapes };", + " for (const name in sources) {", + " let compiled = null;", + " Object.defineProperty(objects, name, { get: () => compiled || (compiled = objectLisp.compile(sources[name], name)) });", + " }", + " return objects;", "})();", end, ].join("\n"); -- 2.51.2