Delivered by Hermies Qwen, gated and deployed by Relay. - Board tip/topple: full continuous rotation when a board's center of mass passes its support edge (Q1: smooth pivot swing, no discrete snap). Falling boards deal impact damage and can kill enemies in their fall path. - Falling-object impact damage: blocks and birds can now kill enemies from vertical drops and side plows (velocity-aware crush gate), chain momentum transfers correctly through stacks (mass-weighted contact), repeated impacts finish tougher enemies. - Landed-bird physics: a bird resting on a board remains a real physical body under continuous gravity (Q2: no lifetime timer). If its platform is destroyed it falls, damages what it lands on (chain reaction), and can kill enemies. - Repo modularized: folders = modules, subfolders = submodules, architecture.md + MODULES.md at root, an agents.md card in every module and submodule (17 total). - Test suite grown from 27 to 47 (20 new RED-watched tests for the above behaviors), all green. Golden differential against the original core confirmed the only behavioral changes are the intended ones. Live at http://10.244.136.121:8093/, independently re-verified by Relay (full suite re-run, module structure check, headless Chromium smoke) before this push. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.8 KiB
JavaScript
70 lines
2.8 KiB
JavaScript
/* ISLE SLINGERS -- physics/camera/index.js (submodule: camera)
|
|
* RC-375D84 S-2 modularization (behavior-neutral split of the original
|
|
* js/core.js monolith). Installs its units into ONE shared physics scope `P`
|
|
* that every physics submodule sees -- the same single-scope semantics the
|
|
* monolith had. Must load before js/physics/index.js (the aggregator that
|
|
* attaches the public ISLE_CORE API). Surface installed: updatePopups, updateCameraTarget, decayCamera.
|
|
*/
|
|
(function (root, factory) {
|
|
var part = factory();
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = part;
|
|
else (root.ISLE_PHYSICS_PARTS = root.ISLE_PHYSICS_PARTS || []).push(part);
|
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
'use strict';
|
|
return function (P) {
|
|
P.updatePopups = function(g, dt) {
|
|
for (var i = g.scorePopups.length - 1; i >= 0; i--) {
|
|
g.scorePopups[i].age += dt;
|
|
g.scorePopups[i].y -= 40 * dt;
|
|
if (g.scorePopups[i].age > 1.4) g.scorePopups.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
P.updateCameraTarget = function(g) {
|
|
var b = P.activeBird(g);
|
|
var cam = g.camera;
|
|
var tx, ty, tz;
|
|
if (b && b.state === 'flying') {
|
|
// in flight: follow the slinger 1:1 (the bird IS the point of interest)
|
|
tx = Math.max(420, Math.min(P.WORLD.w - 420, b.x + 220));
|
|
ty = Math.max(380, Math.min(P.WORLD.ground - 160, b.y + 60));
|
|
tz = 1;
|
|
} else {
|
|
// aiming / waiting: frame the slingshot AND the whole action zone
|
|
// (live blocks + enemies) so P.launch point and target are always
|
|
// visible at once — zooming out when the level needs it. Without
|
|
// this the sling was visible but the fort was off-screen (or
|
|
// vice-versa mid-flight), making aiming pure guesswork.
|
|
var left = g.level.sling.x - 190;
|
|
var right = left + 300;
|
|
for (var i = 0; i < g.blocks.length; i++) {
|
|
var bl = g.blocks[i];
|
|
if (bl.hp > 0) right = Math.max(right, bl.x + bl.w / 2 + 90);
|
|
}
|
|
for (var k = 0; k < g.enemies.length; k++) {
|
|
var en = g.enemies[k];
|
|
if (!en.dead) right = Math.max(right, en.x + en.r + 90);
|
|
}
|
|
var span = right - left;
|
|
tz = Math.min(1, P.VIEW.w / span);
|
|
tx = left + span / 2;
|
|
// ground at ~78% down the view, whatever the zoom
|
|
ty = Math.max(300, Math.min(P.WORLD.ground, P.WORLD.ground - (0.28 * P.VIEW.h) / tz));
|
|
}
|
|
if (cam.snap) { // first frame of a level: land on target, no pan
|
|
cam.x = tx; cam.y = ty; cam.zoom = tz; cam.snap = false;
|
|
return;
|
|
}
|
|
cam.x += (tx - cam.x) * 0.06;
|
|
cam.y += (ty - cam.y) * 0.06;
|
|
cam.zoom = (cam.zoom || 1) + (tz - (cam.zoom || 1)) * 0.08;
|
|
}
|
|
|
|
P.decayCamera = function(g, dt) {
|
|
if (g.camera.shake > 0) {
|
|
g.camera.shake *= Math.pow(0.03, dt); // fast decay
|
|
if (g.camera.shake < 0.3) g.camera.shake = 0;
|
|
}
|
|
}
|
|
};
|
|
});
|