Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Entity/Misc/Mothership.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ClientInputs } from "../../Client";
import { tps } from "../../config";
import { Color, Tank, Stat, ColorsHexCode, ClientBound, TeamFlags } from "../../Const/Enums";
import GameServer from "../../Game";
import ArenaEntity, { ArenaState } from "../../Native/Arena";
import { ArenaState } from "../../Native/Arena";
import { CameraEntity } from "../../Native/Camera";
import { AI, AIState, Inputs } from "../AI";
import Live from "../Live";
Expand Down
19 changes: 13 additions & 6 deletions src/Native/Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,23 @@ export default class EntityManager {
this.collisionManager.forEachCollisionPair(this.handleCollision);

for (let id = 0; id <= this.lastId; ++id) {
const entity = this.inner[id] as ObjectEntity;
const entity = this.inner[id];

// Alternatively, Entity.exists(entity), though this is probably faster.
if (!entity || entity.hash === 0) continue;

if (entity.isPhysical) {
entity.applyPhysics();
}

if (!entity.isChild) {
if (entity.cameraData) { // Cameras are ticked later
continue;
} else if (ObjectEntity.isObject(entity)) {
if (entity.isPhysical) {
entity.applyPhysics();
}

if (!entity.isChild) {
entity.tick(tick);
}
} else {
// Not an object entity (arena)
entity.tick(tick);
}
}
Expand Down
51 changes: 22 additions & 29 deletions src/Physics/HashGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class HashGrid implements CollisionManager {
private hashMap: number[][] = [];
private gameLeftX: number = 0;
private gameTopY: number = 0
private collisionPairsSeen = new Uint32Array(MAX_ENTITY_COUNT * MAX_ENTITY_COUNT / 32)
private collisionPairsSeen = new Uint32Array(MAX_ENTITY_COUNT * (MAX_ENTITY_COUNT - 1) / 2 / 32)

public constructor(game: GameServer) {
this.game = game;
Expand All @@ -50,7 +50,7 @@ export default class HashGrid implements CollisionManager {
public preTick(tick: number): void {
const widthInCells = (this.game.arena.width + (CELL_SIZE - 1)) >> CELL_SHIFT;
const heightInCells = (this.game.arena.height + (CELL_SIZE - 1)) >> CELL_SHIFT;
this.hashMul = widthInCells >> CELL_SHIFT;
this.hashMul = widthInCells;
this.hashMap = Array(widthInCells * heightInCells);
this.queryIdMap.fill(0);
this.lastQueryId = 0;
Expand All @@ -65,7 +65,7 @@ export default class HashGrid implements CollisionManager {
}

public insert(entity: ObjectEntity) {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert() entity outside of tick");
const { sides, size, width } = entity.physicsData.values;
const { x, y } = entity.positionData.values;
const isLine = sides === 2;
Expand Down Expand Up @@ -98,7 +98,7 @@ export default class HashGrid implements CollisionManager {
halfWidth: number,
halfHeight: number
): PackedEntitySet {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot retrieve() entity outside of tick");
const result = this.resultSet;
result.clear();

Expand Down Expand Up @@ -140,7 +140,7 @@ export default class HashGrid implements CollisionManager {
halfHeight: number,
predicate: (entity: ObjectEntity) => boolean
): ObjectEntity | null {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot getFirstMatch() outside of tick");

const startX = (centerX - halfWidth - this.gameLeftX) >> CELL_SHIFT;
const startY = (centerY - halfHeight - this.gameTopY) >> CELL_SHIFT;
Expand Down Expand Up @@ -180,9 +180,9 @@ export default class HashGrid implements CollisionManager {

// No longer used
public retrieveEntitiesByEntity(entity: ObjectEntity): PackedEntitySet {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot retrieveEntitiesByEntity() outside of tick");
const { sides, size, width } = entity.physicsData.values;
const { x, y } = entity.positionData;
const { x, y } = entity.positionData.values;
const isLine = sides === 2;
const halfWidth = isLine ? size / 2 : size;
const halfHeight = isLine ? width / 2 : size;
Expand All @@ -192,7 +192,7 @@ export default class HashGrid implements CollisionManager {
public forEachCollisionPair(
callback: (entityA: ObjectEntity, entityB: ObjectEntity) => void
): void {
if (this.isLocked) throw new Error("HashGrid is locked! Cannot insert entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot forEachCollisionPair() entity outside of tick");
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message says "Cannot forEachCollisionPair() entity outside of tick" but it should probably be "Cannot call forEachCollisionPair() outside of tick" (without the word "entity") to match the pattern of the other error messages and be grammatically correct.

Suggested change
if (this.isLocked) throw new Error("HashGrid is locked! Cannot forEachCollisionPair() entity outside of tick");
if (this.isLocked) throw new Error("HashGrid is locked! Cannot call forEachCollisionPair() outside of tick");

Copilot uses AI. Check for mistakes.

const collisionsSeen = this.collisionPairsSeen;
collisionsSeen.fill(0);
Expand All @@ -213,27 +213,20 @@ export default class HashGrid implements CollisionManager {
const entityB = this.game.entities.inner[eidB] as ObjectEntity;
if (!entityB || entityB.hash === 0) continue;

if (eidA < eidB) {
// Prevent extra-cell duplicates
const pairHash = (eidA << MAX_ENTITY_ID_BITS) | eidB;
const pairHashIndex = pairHash >>> 5;
const pairHashBit = 1 << (pairHash & 31);
if ((collisionsSeen[pairHashIndex] & pairHashBit) !== 0) continue;
collisionsSeen[pairHashIndex] |= pairHashBit;

// Ensure (x, y) -> x.id < y.id
callback(entityA, entityB);
} else {
// Prevent extra-cell duplicates
const pairHash = (eidB << MAX_ENTITY_ID_BITS) | eidA;
const pairHashIndex = pairHash >>> 5;
const pairHashBit = 1 << (pairHash & 31);
if ((collisionsSeen[pairHashIndex] & pairHashBit) !== 0) continue;
collisionsSeen[pairHashIndex] |= pairHashBit;

// Ensure (x, y) -> x.id < y.id
callback(entityB, entityA);
}
// Ensure eidA < eidB for triangular matrix indexing
const [idA, idB] = eidA < eidB ? [eidA, eidB] : [eidB, eidA];
const [entA, entB] = eidA < eidB ? [entityA, entityB] : [entityB, entityA];

// Triangular matrix index: row * (row - 1) / 2 + col, where row > col
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "row * (row - 1) / 2 + col" but the actual formula uses idB and idA. For clarity, consider updating the comment to match the variable names: "Triangular matrix index: idB * (idB - 1) / 2 + idA, where idB > idA".

Suggested change
// Triangular matrix index: row * (row - 1) / 2 + col, where row > col
// Triangular matrix index: idB * (idB - 1) / 2 + idA, where idB > idA

Copilot uses AI. Check for mistakes.
const triangularIndex = (idB * (idB - 1) / 2 + idA);
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The triangular index calculation uses JavaScript division which returns a floating-point number. For correctness and to avoid potential precision issues with large IDs, consider using integer division explicitly. Replace line 221 with: const triangularIndex = Math.floor(idB * (idB - 1) / 2) + idA; or use bit shifting for the division by 2: const triangularIndex = ((idB * (idB - 1)) >> 1) + idA;. While the current implementation will likely work, explicit integer operations make the intent clearer and avoid relying on JavaScript's automatic conversion to 32-bit integers during bitwise operations.

Suggested change
const triangularIndex = (idB * (idB - 1) / 2 + idA);
const triangularIndex = Math.floor(idB * (idB - 1) / 2) + idA;

Copilot uses AI. Check for mistakes.
const arrayIndex = triangularIndex >>> 5;
const bitIndex = triangularIndex & 31;
const bitMask = 1 << bitIndex;

if ((collisionsSeen[arrayIndex] & bitMask) !== 0) continue;
collisionsSeen[arrayIndex] |= bitMask;

callback(entA, entB);
}
}
}
Expand Down