From 1b077ab028367f0601d58e48c1c39e90c11bf127 Mon Sep 17 00:00:00 2001 From: prompt.ac/@jeffrey Date: Tue, 18 Aug 2026 16:54:54 +0000 Subject: [PATCH] oskiewar v75: feet plant on the surface underfoot, never the storey below The grounded pose, the skateboard stance, the board's tilt probes and the low kick's sweep all read the bare terrain heightfield — the arena floor — so a fighter on a rung planted his feet six storeys down and his shins stretched to reach them. One probe now asks for the surface actually underfoot (terrain plus rungs, via surfaceYAt) and a foot hanging past a rung's lip stops at the leg's own reach. These capsules double as hitboxes, so this is combat geometry, not costume. --- xbox/live/oskiewar.js | 35 ++++++++++++++++++++++++++--------- xbox/live/social/manifest.json | 2 +- xbox/live/social/oskiewar-title.jpg | 0 xbox/live/social/oskiewar-title.mp4 | 0 xbox/live/tests/oskiewar.test.mjs | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 5 file(s) changed, 74 insertion(s)(+), 12 deletion(s)(-) diff --git a/xbox/live/oskiewar.js b/xbox/live/oskiewar.js --- a/xbox/live/oskiewar.js +++ b/xbox/live/oskiewar.js @@ -21,7 +21,7 @@ return info; }; // Monotonic count of committed revisions to this piece (next revision included). -const buildVersion = 74; +const buildVersion = 75; const floorY = 1800; // The tower. @jeffrey played the padded room on the console and asked for a // tall map: the fight should happen on the way up, not back and forth across @@ -4235,11 +4235,16 @@ function meleeTarget(player, now) { const pulse = meleePulse(player, now); const spec = meleeSpecFor(player, player.attackKind); const lowKick = player.attackKind === "KICK" && player.lowKick; + const reachX = player.x + player.facing * (spec.reach + spec.swell * pulse); + // A low kick sweeps the ground in front of the kicker — the ground he is + // standing on. The bare terrain probe read the arena floor, so a low kick + // thrown on a rung aimed a storey down through the rung it stood on. + const sweepY = lowKick + ? Math.min(surfaceYAt(reachX, player.y), player.y + 20) - 5 : 0; return { x: player.x + player.facing * (spec.reach + spec.swell * pulse + (lowKick ? 34 * pulse : 0)), - y: lowKick ? terrainFloorAt(player.x + player.facing * - (spec.reach + spec.swell * pulse)) - 5 : player.y - spec.height, + y: lowKick ? sweepY : player.y - spec.height, z: player.z, }; } @@ -6561,6 +6566,13 @@ return { jointX: middleX - dy / distance * height * bend, jointY: middleY + dx / distance * height * bend, targetX, targetY }; }; + // Where a grounded foot may actually plant. The bare terrain probe reads + // the arena floor, so a fighter standing on a rung planted his feet a + // storey down and the shins stretched to reach — the long legs @jeffrey + // called out. The probe now asks for the surface the fighter is actually + // standing on, and a foot hanging past a rung's lip stops at the leg's own + // reach — a short dangle — instead of the floor below. + const footPlant = (footX) => Math.min(surfaceYAt(footX, feet), feet + 20); segment(head.x, head.y + head.radius * .78, neckX, neckY, 10, "neck"); segment(neckX, neckY, x, hipY, 10, "torso"); // An elbow sags downward whichever way the hand actually reaches. Bending @@ -6603,7 +6615,7 @@ } else if (player.skateboard && player.grounded) { // The deck is drawn tilted along the terrain, so each foot plants on // the deck's own top at its x — flat foot heights ran the board // through the shins on every slope. - const deckAt = (footX) => terrainFloorAt(footX) - 6; + const deckAt = (footX) => footPlant(footX) - 6; const plantedX = x + player.facing * 28; const push = Math.sin(poseCycle) * 34; const leadFootX = plantedX + player.facing * 18; @@ -6627,9 +6639,9 @@ const leadFoot = x + player.facing * 42 + stride; const rearKnee = x - player.facing * 16 - stride * .38; const rearFoot = x - player.facing * 28 - stride; // The animation phase still chooses the discrete stride; ground probes - // constrain that frame onto the heightfield without adding new sim steps. - const leadGround = terrainFloorAt(leadFoot); - const rearGround = terrainFloorAt(rearFoot); + // constrain that frame onto the surface underfoot without new sim steps. + const leadGround = footPlant(leadFoot); + const rearGround = footPlant(rearFoot); segment(x, hipY, leadKnee, leadGround - 30, 10, "lead-thigh"); segment(leadKnee, leadGround - 30, leadFoot, leadGround, 10, "lead-shin"); segment(x, hipY, rearKnee, rearGround - 30, 10, "rear-thigh"); @@ -8179,14 +8191,19 @@ : [8, 12, 24]; const displayNow = player.frozenAt || runtime().monotonicUs; if (player.skateboard) { const board = projectPoint(player.x, player.y + 5, player.z); + // The tilt probes ask for the surface the rider is actually on: bare + // terrain reads the arena floor, which would pitch a rung-riding board + // toward the storey below the moment the ground under it sloped. + const boardSurface = (probeX) => + Math.min(surfaceYAt(probeX, player.y), player.y + 20); const leftEdge = player.skateWallSide ? projectPoint(player.x, player.y - 67, player.z) : projectPoint(player.x - 72, - terrainFloorAt(player.x - 72) + 5, player.z); + boardSurface(player.x - 72) + 5, player.z); const boardEdge = player.skateWallSide ? projectPoint(player.x, player.y + 77, player.z) : projectPoint(player.x + 72, - terrainFloorAt(player.x + 72) + 5, player.z); + boardSurface(player.x + 72) + 5, player.z); const reach = Math.max(.5, Math.hypot(boardEdge.x - leftEdge.x, boardEdge.y - leftEdge.y) / 2); const rotation = Math.atan2(boardEdge.y - leftEdge.y, diff --git a/xbox/live/social/manifest.json b/xbox/live/social/manifest.json --- a/xbox/live/social/manifest.json +++ b/xbox/live/social/manifest.json @@ -1,7 +1,7 @@ { "format": "ac.oskiewar.social-preview", "version": 2, - "build": "ee193221c578c861", + "build": "24ef407f1590925c", "theme": "light", "imageWidth": 1200, "imageHeight": 630, diff --git a/xbox/live/social/oskiewar-title.jpg b/xbox/live/social/oskiewar-title.jpg --- a/xbox/live/social/oskiewar-title.jpg +++ b/xbox/live/social/oskiewar-title.jpg diff --git a/xbox/live/social/oskiewar-title.mp4 b/xbox/live/social/oskiewar-title.mp4 --- a/xbox/live/social/oskiewar-title.mp4 +++ b/xbox/live/social/oskiewar-title.mp4 diff --git a/xbox/live/tests/oskiewar.test.mjs b/xbox/live/tests/oskiewar.test.mjs --- a/xbox/live/tests/oskiewar.test.mjs +++ b/xbox/live/tests/oskiewar.test.mjs @@ -1119,8 +1119,12 @@ assert.match(source, /const broad = Math\.sin/); assert.match(source, /const detail = Math\.sin/); assert.match(source, /function drawTerrainSurface\(/); assert.match(source, /player\.y >= terrainFloorAt\(player\.x\)/); - assert.match(source, /const leadGround = terrainFloorAt\(leadFoot\)/); - assert.match(source, /const rearGround = terrainFloorAt\(rearFoot\)/); + // Foot IK reads the same surface physics stands on — terrain plus rungs — + // through one probe, clamped at the leg's own reach past a lip. + assert.match(source, + /const footPlant = \(footX\) => Math\.min\(surfaceYAt\(footX, feet\), feet \+ 20\)/); + assert.match(source, /const leadGround = footPlant\(leadFoot\)/); + assert.match(source, /const rearGround = footPlant\(rearFoot\)/); assert.match(source, /terrainFloorAt\(ball\.x\) - ball\.radius/); assert.match(source, /terrainPhase = terrainSeed\("oskiewar-physics-1-hills"\)/); }); @@ -1293,6 +1297,47 @@ // them the one that shipped. The impact flash is debug-only, so `debugHitboxes` // gates it first and the round-result freeze only decides how long it lasts. assert.match(source, /const impactDebug = debugHitboxes && !roundResult && now < impactHitboxesUntil/); +}); + +// @jeffrey: "legs seem to get rly long when jumping on platforms". The +// grounded pose planted its feet with the bare terrain probe — the arena +// floor — so a fighter standing on a rung six storeys up stretched his shins +// all the way down to it. Feet now plant on the surface underfoot and a foot +// past a rung's lip dangles at the leg's own reach; these capsules double as +// hitboxes, so a leg is no longer shootable a storey below its owner. +test("feet plant on the rung underfoot instead of the storey below", () => { + const { fight } = createFight(); + const player = fight.players[0]; + const stage = fight.stageGeometry(); + const rung = fight.platformTable()[0]; + const legs = (x, y) => { + player.x = x; + player.y = y; + player.vx = 0; + player.grounded = true; + const pieces = fight.runnerWorldGeometry(player, 0).segments + .filter((piece) => piece.part.endsWith("-leg")); + assert.ok(pieces.length >= 4); + return pieces; + }; + const span = (piece) => Math.hypot(piece.x2 - piece.x1, piece.y2 - piece.y1); + const longestOnFloor = Math.max( + ...legs(1500, stage.floorY).map(span)); + // Standing mid-rung: same silhouette as the floor, one storey up. + for (const piece of legs((rung.left + rung.right) / 2, rung.y)) { + assert.ok(span(piece) <= longestOnFloor + 1, + `${piece.role} stretched to ${Math.round(span(piece))}`); + assert.ok(Math.max(piece.y1, piece.y2) <= rung.y + 21, + `${piece.role} reached below the rung`); + } + // Standing on the lip: the forward foot hangs at the leg's reach instead + // of planting on the floor below. + for (const piece of legs(rung.right, rung.y)) + assert.ok(Math.max(piece.y1, piece.y2) <= rung.y + 21, + `${piece.role} reached past the lip`); + // The low kick sweeps the ground underfoot, not the arena floor. + assert.match(source, + /const sweepY = lowKick\n\s*\? Math\.min\(surfaceYAt\(reachX, player\.y\), player\.y \+ 20\) - 5 : 0;/); }); test("fighter geometry connects the head and renders solid capsule joints", () => { -- tangled.sh