Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

arena: HP + ouch on blast, slower lobs, orbit-state reset on zoom

- HP system (100 max). Splash damage falls off linearly with distance,
with a 6-pt floor so even a graze stings. HP-zero death stays on the
platform — no lava plunge required to die anymore.
- Ouch SFX (`playOuch`): brief layered yelp (noise breath + sine "ah" +
triangle undertone) with random tone per hit so back-to-back blasts
don't loop. Volume scales with damage severity.
- Red vignette flash on hit (~0.35s) for hit-feedback beyond the HP
ticker.
- Tuning: GRENADE_SPEED 18 → 8, FUSE 1.0 → 0.9, EXPLOSION_RADIUS 4.5 →
7.5 — Q3 grenades flew way past the firer in the small ±14u arena, so
forward shots never self-blasted. Knockback dialed back (1.3/14/5) so
a single splash doesn't auto-yeet you off the edge.
- 3P scroll bug: applyZoom now resets orbit state. The orbit branch in
sim fired whenever orbitAngle/orbitDistance lingered from a previous
right-drag, displacing the camera (and the reconcile anchor) sideways
every frame after a zoom change. Reset on zoom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+79 -5
+79 -5
system/public/aesthetic.computer/disks/arena.mjs
··· 629 629 sound.synth({ type: "sine", tone: 75, duration: 0.55, volume: 1.1, attack: 0.003, decay: 0.54, pan: 0 }); 630 630 sound.synth({ type: "triangle", tone: 45, duration: 0.40, volume: 0.6, attack: 0.005, decay: 0.39, pan: 0 }); 631 631 } 632 + // "Ouch/ow" — a brief layered yelp. Vocal-ish: noise breath + sine "ah" body 633 + // + triangle undertone, randomised per hit so back-to-back blasts don't read 634 + // as a sample loop. Volume scales with damage so a graze sounds smaller. 635 + function playOuch(sound, severity = 1) { 636 + if (!sound?.synth) return; 637 + const t = 300 + Math.random() * 90; 638 + const v = Math.max(0.25, Math.min(1, severity)); 639 + sound.synth({ type: "noise", tone: 900, duration: 0.04, volume: 0.35 * v, attack: 0.005, decay: 0.035, pan: 0 }); 640 + sound.synth({ type: "sine", tone: t, duration: 0.10, volume: 0.65 * v, attack: 0.005, decay: 0.092, pan: 0 }); 641 + sound.synth({ type: "sine", tone: t * 1.5,duration: 0.07, volume: 0.30 * v, attack: 0.005, decay: 0.064, pan: 0 }); 642 + sound.synth({ type: "triangle", tone: t * 0.7,duration: 0.22, volume: 0.55 * v, attack: 0.010, decay: 0.205, pan: 0 }); 643 + } 644 + 645 + function damagePlayer(amount, doll) { 646 + if (!playerAlive || amount <= 0) return; 647 + playerHP = Math.max(0, playerHP - amount); 648 + lastDamageMs = Date.now(); 649 + playOuch(soundRef, amount / BLAST_DAMAGE_MAX); 650 + if (playerHP <= 0) { 651 + // HP-zero death stays on the platform — no lava plunge required. 652 + playerAlive = false; 653 + deathTickAge = 0; 654 + doll?.setFrozen?.(true); 655 + doll?.clearHeldKeys?.(); 656 + } 657 + } 632 658 633 659 function fireGrenade(cam) { 634 660 if (!cam || myGrenade || !playerAlive || netSpectator) return; ··· 731 757 y: vertImpulse, 732 758 z: -nz * horForce, 733 759 }); 760 + // 💖 Splash damage — linear falloff with a small floor so even a graze stings. 761 + const dmg = BLAST_DAMAGE_FLOOR + (BLAST_DAMAGE_MAX - BLAST_DAMAGE_FLOOR) * falloff; 762 + damagePlayer(dmg, doll); 734 763 // Suspend pmove reconciliation briefly so the local knockback isn't yanked 735 764 // back to the server's prediction (server doesn't know about the blast). 736 765 knockbackGraceUntil = Date.now() + 1200; ··· 838 867 myGrenade = null; 839 868 activeExplosion = null; 840 869 knockbackGraceUntil = 0; 870 + playerHP = MAX_HP; 871 + lastDamageMs = 0; 841 872 system?.fps?.doll?.respawn?.(0, 0); 842 873 return true; 843 874 } ··· 896 927 // shaped blast that knocks the firer (and the cam-doll) outward. The blast 897 928 // can punt the player off the platform and into the lava — that's the trick. 898 929 const GRENADE_RADIUS = 0.18; 899 - const GRENADE_SPEED = 18; // initial m/s along the view dir 930 + const GRENADE_SPEED = 8; // initial m/s — slow enough that a forward 931 + // shot still detonates within EXPLOSION_RADIUS 932 + // of the firer (the arena is only ±14u wide) 900 933 const GRENADE_GRAVITY = 22; // m/s² (lighter than player gravity → arcs) 901 934 const GRENADE_BOUNCE_RESTITUTION = 0.55; 902 935 const GRENADE_BOUNCE_FRICTION = 0.78; // horizontal vel scaling per bounce 903 - const GRENADE_FUSE = 1.0; // seconds before detonation 936 + const GRENADE_FUSE = 0.9; // seconds before detonation 904 937 const GRENADE_REST_VY = 1.0; // settle threshold (avoid eternal jitter) 905 - const EXPLOSION_RADIUS = 4.5; // world units; blast falloff distance 938 + const EXPLOSION_RADIUS = 7.5; // world units; generous so forward shots 939 + // still rocket-jump the firer 906 940 const EXPLOSION_DURATION = 0.45; // seconds the pill stays visible 907 - const EXPLOSION_KICK_HOR = 1.6; // dolly impulse → ~14u horizontal flight 941 + const EXPLOSION_KICK_HOR = 1.3; // dolly impulse → ~12u horizontal flight 908 942 const EXPLOSION_KICK_VERT = 14; // worldYVel impulse (+ is up), units/sec 909 - const EXPLOSION_VERT_BIAS = 6; // baseline upward bump even at edge 943 + const EXPLOSION_VERT_BIAS = 5; // baseline upward bump even at edge 910 944 911 945 let myGrenade = null; // { x, y, z, vx, vy, vz, t } — local single-shot 912 946 let activeExplosion = null; // { x, y, z, age } — local visual effect 913 947 let knockbackGraceUntil = 0; // ms timestamp; while < now() pmove reconcile is suspended 914 948 let soundRef = null; // captured in boot for synth access 915 949 950 + // 💖 Player HP — blast splash damage with linear falloff to the radius edge. 951 + // Center hits (~feet-stomp) score lethal damage, edge hits sting but survive, 952 + // so multiple self-blasts on the platform can kill you without ever touching 953 + // the lava. Reset on respawn. 954 + const MAX_HP = 100; 955 + const BLAST_DAMAGE_MAX = 90; // direct-hit damage at falloff = 1 956 + const BLAST_DAMAGE_FLOOR = 6; // edge-of-radius minimum (so a graze hurts) 957 + let playerHP = MAX_HP; 958 + let lastDamageMs = 0; // wall-clock; drives the red flash overlay 959 + const HP_FLASH_DURATION = 0.35; // seconds the screen tints red after a hit 960 + 916 961 // 🔎 Camera zoom — wheel scroll steps between 1P and 3P at discrete distances. 917 962 // Level 0 = first person. Levels 1..N = third person, pulling the camera back 918 963 // further each click. More close-in steps for shoulder-camera views. ··· 937 982 const d = ZOOM_DISTANCES[zoomLevel]; 938 983 if (zoomLevel === 0) doll.setThirdPerson(false); 939 984 else doll.setThirdPerson(true, d); 985 + // Drop any leftover orbit state when zoom changes. Otherwise a previous 986 + // right-drag's orbitAngle + orbitDistance keep the orbit branch (in sim) 987 + // displacing the camera around the player every frame, which reads as the 988 + // character "flying" sideways the next time you scroll into 3P. 989 + orbiting = false; 990 + orbitAngle = 0; 991 + orbitDistance = 0; 992 + orbitSnapped = false; 940 993 } 941 994 942 995 function tileKey(row, col) { return row * GRID + col; } ··· 2256 2309 advance(); 2257 2310 } 2258 2311 2312 + // 💖 HP bar — number tinted by remaining health (green → yellow → red). 2313 + { 2314 + const pct = Math.max(0, Math.min(1, playerHP / MAX_HP)); 2315 + const hpClr = pct > 0.5 ? [180, 230, 180] 2316 + : pct > 0.25 ? [230, 200, 110] 2317 + : [230, 110, 100]; 2318 + rightLabelMulti([[dim, "hp "], [hpClr, `${Math.ceil(playerHP)}`]], lineY); 2319 + advance(); 2320 + } 2321 + 2259 2322 // Connection: transport + how stale snaps are + lag. 2260 2323 { 2261 2324 const wsOk = !!netServer; ··· 2348 2411 ink("magenta"); 2349 2412 box(px - 6, py, 13, 1); 2350 2413 box(px, py - 6, 1, 13); 2414 + } 2415 + 2416 + // 💢 Damage flash — quick red vignette on the most recent hit so the player 2417 + // gets a hit-feedback pulse beyond the HP number ticking down. 2418 + if (lastDamageMs > 0 && playerAlive) { 2419 + const ageS = (Date.now() - lastDamageMs) / 1000; 2420 + if (ageS < HP_FLASH_DURATION) { 2421 + const k = 1 - ageS / HP_FLASH_DURATION; 2422 + ink(220, 40, 30, Math.floor(k * 90)); 2423 + box(0, 0, screen.width, screen.height); 2424 + } 2351 2425 } 2352 2426 2353 2427 // --- 💀 Death screen overlay (fade-in) ---