Skip to content
Merged
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
27 changes: 27 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 🤖 Instructions for AI Pipeline Agents

Welcome to `Lacikika/Nyxframework`. When developing features or fixing bugs in this codebase, adhere strictly to the following instructions.

---

## 📋 Merge Conflict & Schema Collision Check Protocol

Before submitting a Pull Request, every AI agent **MUST** execute the following checks:

1. **Database Migration Sequence & State Detection:**
- Inspect `FiveM/nyx_core/sql/migrations/index.json` and directory listings in `FiveM/nyx_core/sql/migrations/`.
- Ensure the new migration N+1 is strictly sequential with 3-digit zero-padding (`067_<name>.sql`).
- Register the new SQL migration file in BOTH `FiveM/nyx_core/sql/migrations/index.json` AND `FiveM/nyx_core/server/migrations.lua` (if fallback manifest exists).
- Mirror the SQL migration across any module migration folders (e.g. `FiveM/nyx_<module>/sql/migrations/`).

2. **Branch & Git Collision Prevention:**
- Verify active git branches and origin tracking to prevent branch naming collisions or conflicting migration sequence numbers.

3. **Validation & Test Execution:**
- Execute all verification tools before submission:
```bash
python3 scripts/validator.py
python3 FiveM/scripts/bootstrap_validator.py
pytest
```
- Ensure all 14 resource manifests, Lua scripts, SQL migrations, and test suites pass cleanly with zero errors.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- ============================================================================
-- Nyxframework: nyx_judiciary & nyx_mdt
-- Migration: 067_forensic_ballistics_rifling_and_nibin_integration.sql
-- Description: Forensic ballistics engine, barrel rifling microstriation profiles,
-- ejected shell casing firing pin / breech face signature hashing,
-- and National Integrated Ballistic Information Network (NIBIN) database.
-- Engine: InnoDB | Charset: utf8mb4 | Collation: utf8mb4_unicode_ci
-- ============================================================================

SET FOREIGN_KEY_CHECKS = 0;

-- 1. Create weapon barrel rifling and mechanical signature profiles table
CREATE TABLE IF NOT EXISTS `nyx_ballistics_rifling_profiles` (
`weapon_serial` VARCHAR(64) NOT NULL PRIMARY KEY,
`weapon_model` VARCHAR(64) NOT NULL,
`caliber` VARCHAR(32) NOT NULL,
`rifling_twist` VARCHAR(32) NOT NULL DEFAULT '1:10_RIGHT',
`groove_count` INT NOT NULL DEFAULT 6,
`breech_face_hash` VARCHAR(64) NOT NULL,
`firing_pin_hash` VARCHAR(64) NOT NULL,
`rifling_microstriation_hash` VARCHAR(64) NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_ballistics_model` (`weapon_model`),
INDEX `idx_ballistics_caliber` (`caliber`),
INDEX `idx_ballistics_rifling_hash` (`rifling_microstriation_hash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 2. Create National Integrated Ballistic Information Network (NIBIN) correlation database
CREATE TABLE IF NOT EXISTS `nyx_nibin_database` (
`entry_id` VARCHAR(64) NOT NULL PRIMARY KEY,
`evidence_id` VARCHAR(64) NOT NULL,
`weapon_serial` VARCHAR(64) NULL,
`casing_ejection_hash` VARCHAR(64) NOT NULL,
`rifling_hash` VARCHAR(64) NOT NULL,
`caliber` VARCHAR(32) NOT NULL,
`crime_scene_docket_id` VARCHAR(64) NULL,
`registered_by_citizen_id` VARCHAR(64) NOT NULL,
`match_status` ENUM('UNMATCHED', 'CANDIDATE_MATCH', 'CONFIRMED_MATCH') NOT NULL DEFAULT 'UNMATCHED',
`matched_entry_id` VARCHAR(64) NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_nibin_evidence` (`evidence_id`),
INDEX `idx_nibin_serial` (`weapon_serial`),
INDEX `idx_nibin_casing_hash` (`casing_ejection_hash`),
INDEX `idx_nibin_rifling_hash` (`rifling_hash`),
INDEX `idx_nibin_status` (`match_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

SET FOREIGN_KEY_CHECKS = 1;
1 change: 1 addition & 0 deletions FiveM/nyx_judiciary/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Config.Forensics = {
-- Certified Laboratory Analytical Tests
LabAnalysisTypes = {
['ballistics_match'] = { label = 'Ballistics Striation & Rifling Comparison', fee = 250.00 },
['nibin_crossref'] = { label = 'National Integrated Ballistic Information Network (NIBIN) Cross-Reference', fee = 300.00 },
['dna_profile'] = { label = 'Short Tandem Repeat (STR) DNA Profile', fee = 400.00 },
['gsr_spectrometry'] = { label = 'Scanning Electron Microscopy (GSR-SEM)', fee = 150.00 },
['fingerprint_afis'] = { label = 'Automated Fingerprint Identification (AFIS)', fee = 200.00 },
Expand Down
4 changes: 3 additions & 1 deletion FiveM/nyx_judiciary/fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ shared_scripts {
'shared/courtroom_ui.lua',
'shared/penal_labor.lua',
'shared/acid_etch.lua',
'shared/bail_bonds.lua'
'shared/bail_bonds.lua',
'shared/ballistics.lua'
}

server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/evidence.lua',
'server/ballistics.lua',
'server/dockets.lua',
'server/warrants.lua',
'server/jury.lua',
Expand Down
238 changes: 238 additions & 0 deletions FiveM/nyx_judiciary/server/ballistics.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
--[[
Nyxframework: nyx_judiciary
File: server/ballistics.lua
Description: Authoritative server-side forensic ballistics engine, rifling profile registration,
ejected shell casing telemetry tracking, NIBIN automated cross-referencing & case linking.
]]

Nyx = Nyx or {}
Nyx.Judiciary = Nyx.Judiciary or {}
Nyx.Judiciary.Ballistics = Nyx.Judiciary.Ballistics or {}

-- 1. Ensure or generate weapon barrel rifling profile
function Nyx.Judiciary.Ballistics.GetOrCreateRiflingProfile(weaponSerial, weaponModel)
weaponSerial = weaponSerial or "NYX-UNKNOWN"
weaponModel = weaponModel or "weapon_pistol"

local existing = MySQL.single.await([[
SELECT `weapon_serial`, `weapon_model`, `caliber`, `rifling_twist`, `groove_count`,
`breech_face_hash`, `firing_pin_hash`, `rifling_microstriation_hash`
FROM `nyx_ballistics_rifling_profiles`
WHERE `weapon_serial` = ?
]], { weaponSerial })

if existing then
return existing
end

local caliber = Nyx.Ballistics.GetCaliber(weaponModel)
local salt = Nyx.Judiciary.GenerateSalt and Nyx.Judiciary.GenerateSalt(8) or "RIFLE_SALT_99"
local breechSalt = Nyx.Judiciary.GenerateSalt and Nyx.Judiciary.GenerateSalt(8) or "BREECH_SALT_88"

local riflingHash = Nyx.Ballistics.ComputeRiflingHash(weaponSerial, weaponModel, caliber, salt)
local breechPinHash = Nyx.Ballistics.ComputeBreechPinHash(weaponSerial, "STANDARD_RECTANGULAR", "HEMISPHERICAL_PIN", breechSalt)
local firingPinHash = Nyx.Ballistics.Sha256("PIN:" .. weaponSerial .. ":" .. breechSalt)

MySQL.prepare.await([[
INSERT INTO `nyx_ballistics_rifling_profiles`
(`weapon_serial`, `weapon_model`, `caliber`, `rifling_twist`, `groove_count`,
`breech_face_hash`, `firing_pin_hash`, `rifling_microstriation_hash`)
VALUES (?, ?, ?, '1:10_RIGHT', 6, ?, ?, ?)
]], {
weaponSerial,
weaponModel,
caliber,
breechPinHash,
firingPinHash,
riflingHash
})

return {
weapon_serial = weaponSerial,
weapon_model = weaponModel,
caliber = caliber,
rifling_twist = "1:10_RIGHT",
groove_count = 6,
breech_face_hash = breechPinHash,
firing_pin_hash = firingPinHash,
rifling_microstriation_hash = riflingHash
}
end

-- 2. Enhanced Ballistic Event Registration with Shell Casing Microstriation Ejection
function Nyx.Judiciary.Ballistics.RegisterDischargeEvent(src, weaponHash, coords, ejectionAngleDeg)
local player = Nyx.GetPlayer(src)
if not player then return nil end

local weaponSerial = player:getMetadata("equippedWeaponSerial") or "NYX-UNKNOWN"
local weaponModel = tostring(weaponHash)
local profile = Nyx.Judiciary.Ballistics.GetOrCreateRiflingProfile(weaponSerial, weaponModel)

ejectionAngleDeg = tonumber(ejectionAngleDeg) or 45.0
local ejectSalt = Nyx.Judiciary.GenerateSalt and Nyx.Judiciary.GenerateSalt(8) or "EJECT_SALT_77"
local casingEjectionHash = Nyx.Ballistics.ComputeCasingEjectionHash(weaponSerial, profile.breech_face_hash, ejectionAngleDeg, ejectSalt)

-- Register transient crime scene evidence via main evidence engine
local dropId, rootHash = Nyx.Judiciary.RegisterBallisticEvent(src, weaponHash, coords)

-- Attach enhanced ballistics metadata to active evidence drop
if dropId and Nyx.Judiciary.ActiveEvidence and Nyx.Judiciary.ActiveEvidence[dropId] then
local active = Nyx.Judiciary.ActiveEvidence[dropId]
active.metadata.casingEjectionHash = casingEjectionHash
active.metadata.riflingHash = profile.rifling_microstriation_hash
active.metadata.breechFaceHash = profile.breech_face_hash
active.metadata.firingPinHash = profile.firing_pin_hash
active.metadata.caliber = profile.caliber
active.metadata.weaponSerial = weaponSerial
active.metadata.ejectionAngle = ejectionAngleDeg
end

return dropId, rootHash, casingEjectionHash, profile.rifling_microstriation_hash
end

-- 3. Register Evidence Record in NIBIN Database & Automated Cross-Referencing
function Nyx.Judiciary.Ballistics.RegisterNIBINEntry(src, evidenceId, crimeSceneDocketId)
local officer = Nyx.GetPlayer(src)
if not officer then return false, "OFFICER_NOT_FOUND" end

local evidence = MySQL.single.await([[
SELECT `evidence_id`, `type`, `metadata` FROM `nyx_judiciary_evidence` WHERE `evidence_id` = ?
]], { evidenceId })

if not evidence then
return false, "EVIDENCE_NOT_FOUND"
end

local meta = {}
if type(evidence.metadata) == "string" then
local ok, parsed = pcall(json.decode, evidence.metadata)
if ok and type(parsed) == "table" then meta = parsed end
elseif type(evidence.metadata) == "table" then
meta = evidence.metadata
end

local casingHash = meta.casingEjectionHash or "CASING_HASH_NONE"
local riflingHash = meta.riflingHash or "RIFLING_HASH_NONE"
local caliber = meta.caliber or "9mm"
local weaponSerial = meta.weaponSerial or nil

local entryId = "NIBIN-" .. (Nyx.Judiciary.GenerateUUID and Nyx.Judiciary.GenerateUUID() or tostring(os.time()))

-- Perform automated scan against existing NIBIN entries for matches
local existingEntries = MySQL.query.await([[
SELECT `entry_id`, `evidence_id`, `weapon_serial`, `casing_ejection_hash`, `rifling_hash`, `caliber`, `match_status`
FROM `nyx_nibin_database`
WHERE `caliber` = ? AND `entry_id` != ?
]], { caliber, entryId })

local bestMatchStatus = "UNMATCHED"
local bestMatchedEntryId = nil
local currentEntryObj = {
weaponSerial = weaponSerial,
casingEjectionHash = casingHash,
riflingHash = riflingHash,
caliber = caliber
}

for _, existing in ipairs(existingEntries or {}) do
local targetObj = {
weaponSerial = existing.weapon_serial,
casingEjectionHash = existing.casing_ejection_hash,
riflingHash = existing.rifling_hash,
caliber = existing.caliber
}

local status, score = Nyx.Ballistics.EvaluateNIBINMatch(currentEntryObj, targetObj)
if status == "CONFIRMED_MATCH" then
bestMatchStatus = "CONFIRMED_MATCH"
bestMatchedEntryId = existing.entry_id

-- Update candidate entry as well
MySQL.prepare.await([[
UPDATE `nyx_nibin_database`
SET `match_status` = 'CONFIRMED_MATCH', `matched_entry_id` = ?
WHERE `entry_id` = ?
]], { entryId, existing.entry_id })
break
elseif status == "CANDIDATE_MATCH" and bestMatchStatus ~= "CONFIRMED_MATCH" then
bestMatchStatus = "CANDIDATE_MATCH"
bestMatchedEntryId = existing.entry_id
end
end

-- Insert NIBIN Record
local insertOk = MySQL.prepare.await([[
INSERT INTO `nyx_nibin_database`
(`entry_id`, `evidence_id`, `weapon_serial`, `casing_ejection_hash`, `rifling_hash`,
`caliber`, `crime_scene_docket_id`, `registered_by_citizen_id`, `match_status`, `matched_entry_id`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
]], {
entryId,
evidenceId,
weaponSerial or nil,
casingHash,
riflingHash,
caliber,
crimeSceneDocketId or nil,
officer.citizenId,
bestMatchStatus,
bestMatchedEntryId or nil
})

if not insertOk then
return false, "DATABASE_ERROR"
end

-- Log NIBIN audit block in Chain of Custody
Nyx.Judiciary.AppendCustodyBlock(
evidenceId,
"LAB_ANALYZED",
officer.citizenId,
nil,
"nibin:national_registry",
"NIBIN Correlation Analysis Complete. Status: " .. bestMatchStatus,
{ nibinEntryId = entryId, matchStatus = bestMatchStatus, matchedEntryId = bestMatchedEntryId }
)

return true, {
entryId = entryId,
evidenceId = evidenceId,
matchStatus = bestMatchStatus,
matchedEntryId = bestMatchedEntryId,
casingEjectionHash = casingHash,
riflingHash = riflingHash
}
end

-- 4. Query NIBIN Correlation Records for MDT / Forensic UI
function Nyx.Judiciary.Ballistics.QueryNIBINRecords(filterCaliber, matchStatusOnly)
local conditions = {}
local params = {}

if filterCaliber and filterCaliber ~= "all" and filterCaliber ~= "" then
table.insert(conditions, "`caliber` = ?")
table.insert(params, filterCaliber)
end

if matchStatusOnly and matchStatusOnly ~= "all" and matchStatusOnly ~= "" then
table.insert(conditions, "`match_status` = ?")
table.insert(params, matchStatusOnly)
end

local whereClause = ""
if #conditions > 0 then
whereClause = "WHERE " .. table.concat(conditions, " AND ")
end

local sql = string.format([[
SELECT `entry_id`, `evidence_id`, `weapon_serial`, `casing_ejection_hash`, `rifling_hash`,
`caliber`, `crime_scene_docket_id`, `registered_by_citizen_id`, `match_status`,
`matched_entry_id`, `created_at`
FROM `nyx_nibin_database`
%s
ORDER BY `created_at` DESC
LIMIT 50
]], whereClause)

return MySQL.query.await(sql, params) or {}
end
17 changes: 17 additions & 0 deletions FiveM/nyx_judiciary/server/exports.lua
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,20 @@ end)
exports("ForfeitBailBond", function(docketId, reason)
return Nyx.Judiciary.ForfeitBail(docketId, reason)
end)

-- Forensic Ballistics & NIBIN Integration Exports
exports("GetOrCreateRiflingProfile", function(weaponSerial, weaponModel)
return Nyx.Judiciary.Ballistics.GetOrCreateRiflingProfile(weaponSerial, weaponModel)
end)

exports("RegisterDischargeEvent", function(src, weaponHash, coords, ejectionAngleDeg)
return Nyx.Judiciary.Ballistics.RegisterDischargeEvent(src, weaponHash, coords, ejectionAngleDeg)
end)

exports("RegisterNIBINEntry", function(src, evidenceId, crimeSceneDocketId)
return Nyx.Judiciary.Ballistics.RegisterNIBINEntry(src, evidenceId, crimeSceneDocketId)
end)

exports("QueryNIBINRecords", function(filterCaliber, matchStatusOnly)
return Nyx.Judiciary.Ballistics.QueryNIBINRecords(filterCaliber, matchStatusOnly)
end)
Loading
Loading