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");