first commmit

This commit is contained in:
jakciehan
2026-05-06 08:17:32 +08:00
commit 427a33c55b
269 changed files with 20906 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
import { DropSystem } from '@logic/DropSystem';
import { EnemyType, ItemType } from '@data/Interfaces';
describe('DropSystem — crystal jade deterministic rule (req 7.1)', () => {
it('spawns a crystal jade on exactly the 12th kill', () => {
const ds = new DropSystem({ random: () => 1 });
let crystalEvents = 0;
for (let i = 1; i <= 24; i++) {
const drops = ds.onEnemyKilled(EnemyType.YaoFang, { x: 100, y: 0 });
if (drops.some((d) => d.item === ItemType.CrystalJade)) crystalEvents++;
}
expect(crystalEvents).toBe(2);
});
it('spawns the crystal above the kill point', () => {
const ds = new DropSystem({ random: () => 1 });
for (let i = 0; i < 11; i++) ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
const drops = ds.onEnemyKilled(EnemyType.QingRen, { x: 300, y: 20 });
const crystal = drops.find((d) => d.item === ItemType.CrystalJade);
expect(crystal!.y).toBeGreaterThan(20);
});
});
describe('DropSystem — Chi Ren consecutive rule (req 7.3)', () => {
it('drops dian_wan or shu_wan on the 3rd consecutive Chi Ren kill if RNG <0.5', () => {
const ds = new DropSystem({
dianShuWanProbability: 0.5,
random: (() => {
// probability check then which-item check both pass
const vals = [0.1, 0.2];
let i = 0;
return () => vals[i++ % vals.length];
})(),
});
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 50, y: 10 });
expect(drops.find((d) => d.item === ItemType.DianWan)).toBeDefined();
});
it('does not drop when RNG fails probability', () => {
const ds = new DropSystem({ dianShuWanProbability: 0.5, random: () => 0.95 });
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
expect(drops.filter((d) => d.item !== ItemType.CrystalJade).length).toBe(0);
});
it('non-Chi-Ren kill resets the consecutive counter', () => {
const ds = new DropSystem({ dianShuWanProbability: 1.0, random: () => 0 });
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
const drops = ds.onEnemyKilled(EnemyType.ChiRen, { x: 0, y: 0 });
// Only 1 Chi Ren kill since reset — below threshold.
expect(drops.filter((d) => d.item === ItemType.DianWan || d.item === ItemType.ShuWan).length).toBe(0);
});
});
describe('DropSystem — reset()', () => {
it('zeroes kill counters', () => {
const ds = new DropSystem();
ds.onEnemyKilled(EnemyType.QingRen, { x: 0, y: 0 });
ds.reset();
expect(ds.kills).toBe(0);
});
});