Part 1 final build: Los Santos RP written on camera by the agent

rp_core (sessions, scrypt auth, self-written MySQL-wire driver rp_db),
county-records NUI: residency-file auth, identity-record character
intake, survey-grid spawn, procedural night-city loading screen.
Secrets replaced with .example files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 19:27:48 +00:00
co-authored by Claude Fable 5
parent cb0649016c
commit 80c1d75d2f
41 changed files with 6801 additions and 0 deletions
@@ -0,0 +1,144 @@
-- ---------------------------------------------------------------------------
-- End-to-end check of the account and character data path.
--
-- The first run creates a marker account. Every later run finds it already
-- there and verifies it came back byte-for-byte, which is what "persists
-- between restarts" actually means. Run `selftest_reset` to clear it.
-- ---------------------------------------------------------------------------
local MARKER = 'zz_selftest'
local PASSWORD = 'correct horse 7'
local passed, failed = 0, 0
local function check(name, ok, detail)
if ok then
passed = passed + 1
print(('^2 PASS^7 %s'):format(name))
else
failed = failed + 1
print(('^1 FAIL^7 %s ^1%s^7'):format(name, detail or ''))
end
end
local function hash(pw)
local p = promise.new()
exports.rp_core:hashPassword(pw, function(h, err) p:resolve({ h = h, err = err }) end)
local r = Citizen.Await(p)
return r.h, r.err
end
local function verify(pw, stored)
local p = promise.new()
exports.rp_core:verifyPassword(pw, stored, function(ok, err) p:resolve({ ok = ok, err = err }) end)
return Citizen.Await(p).ok
end
CreateThread(function()
if not DB.WaitReady(30000) then
print('^1[selftest] database never became ready^7')
return
end
print('^5[selftest]^7 ---- account + character data path ----')
-- 1. password hashing ----------------------------------------------------
local h, herr = hash(PASSWORD)
check('scrypt hash produced', type(h) == 'string' and h:sub(1, 7) == 'scrypt$', tostring(herr))
check('correct password verifies', verify(PASSWORD, h))
check('wrong password rejected', not verify(PASSWORD .. 'x', h))
check('empty password rejected', not verify('', h))
local h2 = hash(PASSWORD)
check('same password hashes differently (salted)', h ~= h2)
check('second hash also verifies', verify(PASSWORD, h2))
check('corrupt hash rejected safely', not verify(PASSWORD, 'not-a-hash'))
-- 2. the account row ------------------------------------------------------
local existing = DB.Single('SELECT id, password_hash, created_at FROM accounts WHERE username = ?', { MARKER })
local firstRun = existing == nil
local accountId
if firstRun then
accountId = DB.Insert(
'INSERT INTO accounts (username, password_hash, license) VALUES (?, ?, ?)',
{ MARKER, h, 'selftest-license' })
check('account created', type(accountId) == 'number' and accountId > 0)
print('^3[selftest]^7 marker account created - restart the server to prove persistence')
else
accountId = existing.id
check('account survived restart', true)
check('stored hash still verifies after restart', verify(PASSWORD, existing.password_hash))
print(('^5[selftest]^7 marker account has existed since %s'):format(tostring(existing.created_at)))
end
-- 3. the character row, including the appearance JSON ---------------------
local appearance = Appearance.Sanitise({
model = 'f',
parents = { father = 12, mother = 33, shapeMix = 0.62, skinMix = 0.25 },
hair = { style = 14, colour = 9, highlight = 2 },
features = { ['0'] = 0.4, ['13'] = -0.75 },
overlays = { eyebrows = { index = 5, opacity = 0.8, colour = 3 } },
components = { jacket = { drawable = 42, texture = 3 } },
})
local charId = DB.Scalar('SELECT id FROM characters WHERE account_id = ? AND deleted = 0 LIMIT 1', { accountId })
if not charId then
charId = DB.Insert([[
INSERT INTO characters (account_id, slot, first_name, last_name, dob, gender, backstory, appearance)
VALUES (?, 0, ?, ?, ?, 'f', ?, ?)
]], { accountId, 'Zztest', 'Persistence', '1991-04-18',
'Created by the self test.', json.encode(appearance) })
check('character created', type(charId) == 'number' and charId > 0)
else
check('character survived restart', true)
end
local row = DB.Single('SELECT * FROM characters WHERE id = ?', { charId })
check('character reads back', row ~= nil)
if row then
local back = json.decode(row.appearance)
check('appearance JSON round trip', json.encode(Appearance.Sanitise(back)) == json.encode(appearance),
'appearance differs after a round trip')
check('float precision kept', math.abs(back.parents.shapeMix - 0.62) < 0.0001,
tostring(back.parents.shapeMix))
check('negative feature kept', math.abs(back.features['13'] + 0.75) < 0.0001,
tostring(back.features['13']))
check('starting balances applied', tonumber(row.cash) == Config.Money.startingCash
and tonumber(row.bank) == Config.Money.startingBank,
('cash=%s bank=%s'):format(tostring(row.cash), tostring(row.bank)))
end
-- 4. the constraints the session code relies on ---------------------------
local dupe, dupeErr = DB.Insert(
'INSERT INTO accounts (username, password_hash) VALUES (?, ?)', { MARKER, h })
check('duplicate username refused by the database', dupe == nil, tostring(dupe))
local dupeName = DB.Insert([[
INSERT INTO characters (account_id, slot, first_name, last_name, dob, gender)
VALUES (?, 1, 'Zztest', 'Persistence', '1991-04-18', 'f')
]], { accountId })
check('duplicate character name refused by the database', dupeName == nil)
local orphan = DB.Insert([[
INSERT INTO characters (account_id, slot, first_name, last_name, dob, gender)
VALUES (999999, 0, 'Zzorphan', 'Nobody', '1991-04-18', 'm')
]], {})
check('character cannot reference a missing account', orphan == nil)
print(('^5[selftest]^7 ==== %d passed, %d failed ===='):format(passed, failed))
if failed == 0 then
print(firstRun and '^2[selftest] data path OK (first run)^7' or '^2[selftest] data path OK and persistent^7')
else
print('^1[selftest] PROBLEMS FOUND^7')
end
end)
RegisterCommand('selftest_reset', function()
CreateThread(function()
DB.Update('DELETE FROM accounts WHERE username = ?', { MARKER }) -- characters cascade
print('^3[selftest]^7 marker account removed')
end)
end, true)