h
This commit is contained in:
parent
2cc2f003df
commit
61718e2981
18 changed files with 572 additions and 190 deletions
|
|
@ -35,7 +35,8 @@ checks = {
|
|||
'direct feed is food-only': 'if (foodGift) {' in read('js/tarinai_direct_feeding_system.js') and 'recordDirectFeed?.' in read('js/tarinai_direct_feeding_system.js'),
|
||||
'ritual ignition checks state before mutation': 'const ignitedDuringBirthRitual = !already' in read('js/tarinai_disease_nest.js'),
|
||||
'soccer unlock follows fatal ball impact': 'if (t.dead && ball.type === "ball")' in read('js/collision_response_system.js'),
|
||||
'self-zunchi unlock checks owner and never-stopped': 'item.achievementBurstOwnerId === t.id && item.achievementBurstNeverStopped !== false' in read('js/item_dynamic_zunchi_system.js'),
|
||||
'self-zunchi return collision is armed only after owner clearance': 'achievementBurstClearedOwner = false' in read('js/world_combat_effects.js') and 'updateBurstOwnerClearance(item, worldRef)' in read('js/item_dynamic_zunchi_system.js') and 'if (isBurstOwner && item.achievementBurstClearedOwner !== true) continue' in read('js/item_dynamic_zunchi_system.js'),
|
||||
'self-zunchi unlock survives fatal ID release': 'const hitTarinaiIdBeforeDamage = String(t.id || t.releasedLiveId || "")' in read('js/item_dynamic_zunchi_system.js') and 'const eligibleSelfZunchiDeath = Boolean(' in read('js/item_dynamic_zunchi_system.js') and 'if (t.dead && eligibleSelfZunchiDeath)' in read('js/item_dynamic_zunchi_system.js'),
|
||||
'held launched zunchi is disqualified': 'if (item.playerHeld || item._heldByPlayer)' in read('js/item_dynamic_zunchi_system.js') and 'target.achievementBurstNeverStopped = false' in read('js/ui_input_shared.js'),
|
||||
'generation runs persist in saves': 'achievementNaturalSlaveGenerations' in read('js/snapshot_system.js') and 'achievementNaturalKingGenerations' in read('js/snapshot_system.js'),
|
||||
'direct-feed field progress persists in saves': 'achievementDirectFeedCount' in read('js/snapshot_system.js'),
|
||||
|
|
|
|||
|
|
@ -161,6 +161,12 @@ function findCard(h,id){const stack=[h.elements.get("achievementList")];while(st
|
|||
for(let i=0;i<5;i++){const ant={world:ew2};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ew2});h.api.recordAntKilled({world:ew2,ant});}
|
||||
ew2.tarinai[0].dead=true;const resetAnt={world:ew2};h.api.recordAntDamageSource(resetAnt,{type:"firecracker"},{world:ew2});h.api.recordAntKilled({world:ew2,ant:resetAnt});
|
||||
assert((ew2.achievementEnemyAntKills||0)===0,"population below 30 did not reset enemy streak");
|
||||
h=harness();const ewDip={time:20,tarinai:alive(30)};
|
||||
for(let i=0;i<5;i++){const ant={world:ewDip};h.api.recordAntDamageSource(ant,{type:"firecracker"},{world:ewDip});h.api.recordAntKilled({world:ewDip,ant});}
|
||||
ewDip.tarinai[0].dead=true;h.api.recordDeath({world:ewDip,tarinai:ewDip.tarinai[0],reason:"天寿を全うした"});
|
||||
assert((ewDip.achievementEnemyAntKills||0)===0,"population dip without an ant kill preserved the enemy streak");
|
||||
ewDip.tarinai.push({dead:false});const afterDip={world:ewDip};h.api.recordAntDamageSource(afterDip,{type:"firecracker"},{world:ewDip});h.api.recordAntKilled({world:ewDip,ant:afterDip});
|
||||
assert((ewDip.achievementEnemyAntKills||0)===1,"enemy streak did not restart from one after population recovery");
|
||||
const ordinary={world:ew2};h.api.recordAntDamageSource(ordinary,{type:"food"},{world:ew2});h.api.recordAntKilled({world:ew2,ant:ordinary});assert((ew2.achievementEnemyAntKills||0)===0,"non-danger ant kill counted");
|
||||
h=harness();const ew3={time:20,tarinai:alive(30)};const overwritten={world:ew3};h.api.recordAntDamageSource(overwritten,{type:"firecracker"},{world:ew3});h.api.recordAntDamageSource(overwritten,{id:"t0"},{world:ew3});h.api.recordAntKilled({world:ew3,ant:overwritten});assert((ew3.achievementEnemyAntKills||0)===0,"stale danger attribution survived later non-danger damage");
|
||||
|
||||
|
|
|
|||
|
|
@ -2016,6 +2016,15 @@ const vm = require('vm');
|
|||
const context = {{
|
||||
console, globalThis: null, window: null,
|
||||
clamp: (v, lo, hi) => Math.max(lo, Math.min(hi, Number(v) || 0)),
|
||||
TarinaiGeometry: {{
|
||||
pointSegmentDistance(px, py, ax, ay, bx, by) {{
|
||||
const sx = bx - ax, sy = by - ay;
|
||||
const len2 = sx * sx + sy * sy;
|
||||
if (len2 <= 1e-12) return Math.hypot(px - ax, py - ay);
|
||||
const t = Math.max(0, Math.min(1, ((px - ax) * sx + (py - ay) * sy) / len2));
|
||||
return Math.hypot(px - (ax + sx * t), py - (ay + sy * t));
|
||||
}},
|
||||
}},
|
||||
audio: {{ damage() {{}}, play() {{}}, death() {{}} }},
|
||||
}};
|
||||
context.globalThis = context;
|
||||
|
|
|
|||
129
scripts/self_zunchi_regression_audit.js
Normal file
129
scripts/self_zunchi_regression_audit.js
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const vm = require("vm");
|
||||
const path = require("path");
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) throw new Error(message);
|
||||
}
|
||||
|
||||
function createHarness({ owner = true, lethal = true, neverStopped = true, clearedOwner = true } = {}) {
|
||||
let unlocks = 0;
|
||||
let damageCalls = 0;
|
||||
const ownerId = "t-owner";
|
||||
const tarinai = {
|
||||
id: owner ? ownerId : "t-other",
|
||||
releasedLiveId: "",
|
||||
dead: false,
|
||||
x: 100,
|
||||
y: 100,
|
||||
radius: 20,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
fallTimer: 0,
|
||||
fallMax: 0,
|
||||
enterPanic() {},
|
||||
};
|
||||
const item = {
|
||||
type: "zunchi",
|
||||
dead: false,
|
||||
x: 100,
|
||||
y: 100,
|
||||
prevX: 100,
|
||||
prevY: 100,
|
||||
r: 14,
|
||||
amount: 240,
|
||||
vx: 200,
|
||||
vy: 0,
|
||||
achievementBurstOwnerId: ownerId,
|
||||
achievementBurstNeverStopped: neverStopped,
|
||||
achievementBurstClearedOwner: clearedOwner,
|
||||
};
|
||||
const world = {
|
||||
time: 10,
|
||||
w: 1000,
|
||||
h: 1000,
|
||||
tarinai: [tarinai],
|
||||
drawListDirty: false,
|
||||
liveTarinaiById(id) { return tarinai.id === id && !tarinai.dead ? tarinai : null; },
|
||||
nearbyTarinai() { return [tarinai]; },
|
||||
isTarinaiHiddenInNestBox() { return false; },
|
||||
applyImpactDamage(target) {
|
||||
damageCalls += 1;
|
||||
if (!lethal) return true;
|
||||
target.dead = true;
|
||||
target.releasedLiveId = target.id;
|
||||
target.id = "";
|
||||
return true;
|
||||
},
|
||||
spawnFallEffect() {},
|
||||
effects: [],
|
||||
};
|
||||
|
||||
const context = {
|
||||
console,
|
||||
globalThis: null,
|
||||
window: undefined,
|
||||
CONFIG: { worldPadding: 30 },
|
||||
clamp: (value, min, max) => Math.max(min, Math.min(max, value)),
|
||||
distXY: (ax, ay, bx, by) => Math.hypot(ax - bx, ay - by),
|
||||
rand: () => 0,
|
||||
Effect: function Effect() {},
|
||||
TarinaiPhysics: { applyKinematicItemMotion() {} },
|
||||
TarinaiItemDynamicBallSystem: { resolveObstacleCollisions() {} },
|
||||
TarinaiAchievements: {
|
||||
recordSelfZunchiDeath() { unlocks += 1; },
|
||||
},
|
||||
};
|
||||
context.globalThis = context;
|
||||
vm.createContext(context);
|
||||
const source = fs.readFileSync(path.join(__dirname, "..", "js", "item_dynamic_zunchi_system.js"), "utf8");
|
||||
vm.runInContext(source, context, { filename: "item_dynamic_zunchi_system.js" });
|
||||
return { context, world, tarinai, item, get unlocks() { return unlocks; }, get damageCalls() { return damageCalls; } };
|
||||
}
|
||||
|
||||
function runOne(options = {}) {
|
||||
const h = createHarness(options);
|
||||
h.context.TarinaiItemDynamicZunchiSystem.updateMotion(h.item, 0.016, h.world);
|
||||
return h;
|
||||
}
|
||||
|
||||
let result = runOne({ owner: true, lethal: true, neverStopped: true, clearedOwner: true });
|
||||
assert(result.unlocks === 1, "fatal owner return collision should unlock even after live ID release");
|
||||
|
||||
result = runOne({ owner: true, lethal: false, neverStopped: true, clearedOwner: true });
|
||||
assert(result.unlocks === 0, "nonfatal owner collision must not unlock");
|
||||
|
||||
result = runOne({ owner: false, lethal: true, neverStopped: true, clearedOwner: true });
|
||||
assert(result.unlocks === 0, "fatal collision with another tarinai must not unlock");
|
||||
|
||||
result = runOne({ owner: true, lethal: true, neverStopped: false, clearedOwner: true });
|
||||
assert(result.unlocks === 0, "stopped or player-held burst zunchi must remain disqualified");
|
||||
|
||||
// Exact route prerequisite: the emitted zunchi begins inside its owner, but that
|
||||
// spawn overlap must neither damage the owner nor consume the later return hit.
|
||||
result = runOne({ owner: true, lethal: true, neverStopped: true, clearedOwner: false });
|
||||
assert(result.unlocks === 0, "spawn overlap must not unlock");
|
||||
assert(result.damageCalls === 0, "spawn overlap must not damage or slow the owner collision path");
|
||||
assert(result.item.achievementBurstClearedOwner === false, "spawn overlap must remain uncleared while still inside owner");
|
||||
|
||||
// Move outside once (the route's outbound leg / bounce-fence approach), then
|
||||
// return at lethal speed. This must unlock.
|
||||
result = createHarness({ owner: true, lethal: true, neverStopped: true, clearedOwner: false });
|
||||
result.item.x = 180;
|
||||
result.item.prevX = 100;
|
||||
result.item.vx = 320;
|
||||
result.context.TarinaiItemDynamicZunchiSystem.updateMotion(result.item, 0.016, result.world);
|
||||
assert(result.item.achievementBurstClearedOwner === true, "burst zunchi must arm after clearing its owner");
|
||||
assert(result.damageCalls === 0, "outbound leg must not damage the owner");
|
||||
result.item.x = 100;
|
||||
result.item.prevX = 180;
|
||||
result.item.vx = -900; // representative accelerated return after a bounce fence
|
||||
result.item.vy = 0;
|
||||
result.world.time += 0.2;
|
||||
result.context.TarinaiItemDynamicZunchiSystem.updateMotion(result.item, 0.016, result.world);
|
||||
assert(result.unlocks === 1, "cleared, accelerated return collision with owner must unlock when fatal");
|
||||
|
||||
console.log("[OK] self-zunchi spawn-clear-return regression cases passed");
|
||||
Loading…
Add table
Add a link
Reference in a new issue