# How Progression: current inventory and migration plan
This describes the live `client.lua` and `server.lua` as audited in September 2026 and the planned migration. The `.bak*` and `.pre-*` files are historical copies; `fxmanifest.lua` still runs only `client.lua` and `server.lua` as gameplay entry points. Do not infer live behavior from the copies.
## Live inventory
| Area | Implemented behavior |
| --- | --- |
| Introduction | Appearance save triggers UNKNOWN's one-time NPWD SMS and sets `unknown_introduced` and Crowbar stage 1. Login sync restores Crowbar and Mack/Vinny state. `/testunknown` and `/prog unknown` exercise it. |
| Crowbar world | Local Clay ped, smoking scenario, talk target, map blip, locked frozen Daemon. Talk dialog chooses missions by `crowbar_stage`; active mission suppresses interaction. |
| Crowbar 1, The Introduction | Server-spawned Emperor, crew keys, retrieve/return objectives and blips/marker; initiator gets $500, eligible crew get 5 Crowbar REP and stage 2. Startup removes leftover mission Emperors. |
| Crowbar 2, Get Some Wheels | Server-spawned locked Premier, three lockpicks per participant; delivery persists a Premier through `qbx_vehicles:CreatePlayerVehicle` for the owner, with submitted properties; eligible crew get 5 REP/stage 3 and Crowbar SMS. Physical mission Premier is deliberately retained. |
| Crowbar 3, Quick Score | Harmony store index 13, one unloaded pistol to initiator; `ff_storerobbery` event advances from robbery to losing police. Client escape monitoring completes; eligible crew get 5 REP/stage 4 and marked-cash SMS. |
| Crowbar 4, Wash the Cash | Mack dialog opens Gabe route, Gabe/money-wash event advances objective, `randol_moneywash` completion event ends mission; eligible crew get 5 REP/stage 5 and Crowbar SMS. Mack stage becomes 1 when spoken to. Gabe is currently a blip/location, not a spawned ped here. |
| Crowbar 5, Buy In | Temporary ox_inventory stash with `crowbar_package`, permanent local crate/target, inventory swap hook, package-carrier ambush with two trucks and Lost peds, escape/return objective, death removes package. Eligible crew get 5 REP/stage 6; carrier gets $750. |
| Mack world | Mack ped and Deke ped physically spawn at resource start, plus festival bus, damaged Emperor, forklift, six wandering workers. Mack interaction is gated by server state; blip is unlocked separately. Deke opens ox_inventory `DekeTools`. |
| Mack 1, Easy Rider | Accepted crew each get `WEAPON_BAT`; initiator spawns 1–5 mission Sanchezes. Leave yard, trash detour car, deliver bikes to Vinny, then speak to Vinny. Server tracks delivered bike indices and crew state; each eligible participant gets $300, 5 Mack REP and stage 2. Several Mack SMS messages drive the detour. |
| Vinny world/service | Vinny ped/blip and five local locked frozen display Sanchezes; target calls existing `qbx_vehicleshop` purchase. Delayed SMS 60–90 seconds after Easy Rider completion saves `vinny_fence_unlocked`. Persistent fence marker allows up to three unowned non-mission Sanchezes per player per game day for one `markedbills` item worth $200 each. Server checks ownership, location, quota, item capacity and vehicle deletion. |
| Cosmic Dave | Local Omega ped with partying scenario; no blip, interaction, phone behavior or mission in this resource yet. |
`fxmanifest.lua` declares ox_lib, oxmysql and runtime dependencies and loads one gameplay script per side. `client.lua` mixes contact presentation, dialogs, local scenery, mission vehicles/peds/props, blips/GPS, per-frame markers, mission polling, targeting, inventory use and NPWD UI bridge. `server.lua` mixes schema alteration, SQL reads/writes, crew snapshots/rewards/tracker, phone SMS, mission registration/state, entity validation, fence economy, login restoration, developer commands and cleanup. Passive contact/location definitions and an unregistered mission framework now exist; neither drives current gameplay.
## Foundation implemented, migration pending
`shared/contacts/*.lua` holds seven passive story contact definitions; `shared/contacts/init.lua` checks IDs and permanent phone-number uniqueness when required. `shared/locations.lua` mirrors mission locations, `shared/mission_ids.lua` defines string mission/phase IDs and parses human `/prog` notation, and `server/services/mission_registry.lua` supplies an instance registry without registering missions or events. `shared/debug_contact.lua` records the temporary Michael fixture's avatar separately. The manifest packages shared files but does not run them. Legacy `client.lua` and `server.lua` remain authoritative for every active spawn, interaction, mission, SMS, SQL write, event, callback, export and command. Definitions are verified copies and become authoritative only as individual systems migrate.
Files under `shared/contacts` are data descriptors, with optional explicit callback IDs for interactions. Functions, live handles and side effects belong in services. Contact descriptors should allow `world.alwaysPresent` independent of `services.requires`, `blip.requires`, and `phone.requires`. Use ordered manifest loading or explicit `require` with one bootstrap; do not duplicate event registration during migration. No new SQL table or phone-number allocator is required for this layout.
## Move map (preserve event/export names)
| Current functions/events or blocks | Destination |
| --- | --- |
| `getProgression`, `getPlayerProgression`, `setCrowbarProgression`, `advanceCrowbar`, schema ALTER and SQL projection | `server/services/progression.lua`; preserve existing columns and initialization timing |
| Repeated `*MissionN`, `*MissionNByCitizenId`, `clear/start/complete`, active lookup | `server/services/mission_registry.lua` plus individual server mission files |
| All `start/phase/cleanup/reward` client events, mission polling and spawned mission entities | Corresponding `client/missions/<contact>/<mission>.lua` |
| `createCrowbar`, `createMack`, `createDeke`, `createVinny`, `createCosmicDave`, display bikes and scrapyard ambience | Contact definitions plus `client/services/contacts.lua` and `client/services/world.lua` |
| `create*Blip`, `setMackMission1Route`, all `SetNewWaypoint`, mission/fence `DrawMarker` threads | `client/services/routes.lua` and `markers.lua`, driven by objective views |
| All `updatePartyTasks`/`sendPartyNotification` calls | `server/services/crew_tracker.lua`; preserve exact task labels and status types initially |
| `fenceSanchez`, fence count/day, leave-bike event | `server/services/vinny_fence.lua`, `client/services/world.lua`; keep separate from Easy Rider mission |
| `useBodyArmor` export | `client/services/body_armor.lua`; preserve export name used by ox_inventory |
| `onResourceStop`, mission entity deletion and local target cleanup | Each module's cleanup registered through one lifecycle coordinator |
The move map covers the public event families listed in each mission section, not a rename plan. Keep current event names during extraction so external resources and running clients continue to communicate.
### Public handler inventory by destination
Keep these names while moving handlers. `client:` and `server:` below expand to `how_progression:client:` and `how_progression:server:`.
| Phone | `client:addPhoneContact`; NPWD exports are calls, not how_progression event registrations |
| Lifecycle | `onResourceStop` server handler; client cleanup currently spread across unload and mission handlers |
The body-armor `useBodyArmor` client export and commands `/testunknown`, `/skipcrowbar1`, `/prog`, `/givemarked`, `/testcontact` are also public interfaces. No handler for Gabe/Deke/Cosmic Dave missions exists today.
## Migration order and gates
1. Freeze a behavior inventory: event names, SQL columns, item names, text, coordinates, rewards, phase behavior and external call sites. Record a small solo/crew smoke test for every live mission and `/prog` branch.
2. Add shared contact/location data without switching runtime use. Verify values against live Lua. Move only pure constants and phone numbers into descriptors; reload and test contact visibility.
3. Extract server progression, phone and participant helpers behind existing call signatures. Keep SQL and NPWD calls identical. Test login, SMS, stage sync and one crew snapshot.
4. Extract client world, contact ped/asset, blip and marker services while preserving physical spawn timing and gated access separately. Test reconnect, repeated sync, resource restart and reset.
5. Add mission registry and crew tracker adapters, initially routing to old mission handlers. Preserve exact task payloads and `citizenid` maps. Test solo/crew eligibility.
6. Move one complete mission at a time, with its client and server parts together: Crowbar 1–5, then Easy Rider. At each step, run start, phase, completion, abort/reset and reconnect checks. Do not move a shared helper simultaneously with a mission unless required.
7. Extract Vinny fence, body-armor export and debug commands. Implement deterministic `/prog` reconstruction one mission/phase at a time, preserving existing shorthand until replaced and tested.
8. Once all callers use modules, remove the old live blocks only after event/export and in-game parity checks. Historical copies remain untouched unless separately approved.
Every intermediate manifest must load each event, callback and export exactly once. Keep `client.lua`/`server.lua` as temporary bootstraps if that makes extraction safer.
## Risks and audit findings
- The SQL table creation DDL is not in this resource; only the three Vinny fence columns are altered at startup. Inspect the live schema before changing SQL. `getProgression` inserts a default row and then reads it.
- Mission state is memory-only, generally one global instance per mission plus a citizenid map. Resource restart loses active mission state. Existing stop cleanup explicitly deletes only Crowbar 1/2 server vehicles. Client world assets/targets and other mission entities have uneven cleanup. Treat as migration risks, not permission to change gameplay in the extraction pass.
- Several mission starts call `clear*` on a global mission, which can replace another crew's mission. Easy Rider instead rejects a concurrent start. Preserve current behavior until a separate multiplayer design decision.
-`participantIsValid` compares the snapshotted source's current citizenid; a reconnect with a new source does not regain active mission participation. This is distinct from durable identity for progression. Improve through citizenid resolution only with deliberate mission-lifecycle tests.
-`clearMackMission1`, `setCrowbarProgression`, `advanceCrowbar`, and client `unlockMack`/`unlockGabe` have no in-resource caller in the live files. They may be intended APIs/legacy; search external call sites before removal. `testcontact` intentionally randomizes a temporary Michael number; it must not influence permanent contact numbers.
-`/prog` parses integer text components rather than Lua floats, but current shortcuts depend on an already active mission for most later phases; phase 5.3 has a distinct client event path. These are not deterministic reconstruction yet.
-`getCrowbarActiveMission` and `getMackMission1State` are separate lookup paths. Crew task status values mix `true/false` and `current/pending/done`; standardize only after confirming `bs_groupsystem` behavior.
- The resource uses `ff_storerobbery`, `randol_moneywash`, `qbx_vehicles`, `qbx_vehicleshop`, `ox_inventory`, `ox_target`, `qbx_vehiclekeys`, NPWD and `bs_groupsystem` APIs beyond the manifest dependency list. Preserve server.cfg ordering; inspect installed APIs before adding dependencies.
- The Vinny daily quota uses a client-provided GTA calendar date and SQL count. The server validates shape and monotonicity but does not independently derive the game day. This is a trust boundary to review separately.
-`client.lua.bak*` and `server.lua.bak*` are not loaded, but are numerous and easy to mistake for live code. Keep a clear live-file index; do not delete them as incidental cleanup.
Each significant character gets a dedicated data definition during migration. Fields may include `id`, `displayName`, fixed `phone.number`, `phone.avatarUrl`, `ped.model`, position/heading, scenario/animation, invincible/frozen/blocking settings, blip, interaction ID/label/gate, progression gate, world assets and mission IDs. Omit unknown fields; do not fabricate a phone number or ped. Runtime services spawn and gate them. Permanent 555 numbers must remain stable because NPWD threads use them.
| Contact | Current implementation |
| --- | --- |
| UNKNOWN | Phone-only introduction, fixed `555-0100`; no ped/world position in this resource. |
| Crowbar | `ig_clay` at `vec4(1699.67,3775.30,34.74,207.22)`, smoking, invincible/frozen; talk target and blip after introduction; frozen locked Daemon at `1699.70,3771.63,34.53,258.21`; five missions; `555-7384`. |
| Mack | `s_m_y_xmech_01` at `vec4(2366.60,3154.91,48.21,17.84)`, clipboard, invincible/frozen; physical ped always created at startup, talk gated, blip unlocked; Easy Rider; `555-6842`. Scrapyard bus, junk Emperor, forklift and six workers are associated world assets. |
| Gabe | Current resource defines a money-laundering blip/location at `vec3(2340.96,3126.11,48.21)` and Crowbar 4 step; no Gabe ped/model or phone number here. Actual wash mechanics live in `randol_moneywash`. |
| Deke | `s_m_m_autoshop_02` at `vec4(2359.89,3136.74,48.21,110.90)`, impatient scenario, invincible/frozen, spawned with Mack. Talk target opens `ox_inventory` shop `DekeTools`; no phone number, blip or mission here. |
| Vinny | `a_m_m_business_01` at `vec4(1362.57,3602.49,34.96,236.75)`, invincible/frozen; spawned during Easy Rider and restored with fence unlock, blip, five frozen display Sanchezes and purchase targets. Permanent fence point `vec4(1356.79,3617.37,34.88,105.38)` after delayed SMS; `555-3176`. |
| Cosmic Dave | `ig_omega` at `vec4(2447.63,3775.08,41.43,23.30)`, partying scenario, invincible/frozen, physically spawned at startup. No target, blip, number or mission yet. |
Scrapyard workers and Crowbar 5 Lost ambushers are ambient/mission actors, not contact definitions. The `testcontact` Michael contact is a temporary debug fixture with randomized number, not a permanent contact. Preserve all existing coordinates/models from the live Lua during extraction; this table is an index, not a replacement for source values.
World existence, blip visibility, interaction eligibility, mission access, phone availability and fence/shop service availability need independent gates. In particular, Mack/Deke/Cosmic Dave physical presence is unconditional today, while Crowbar and Vinny are currently spawned by unlock/mission flow.
`getMissionParticipants` uses `bs_groupsystem:getPlayerPartyId(citizenid)` and `sendToPartyMembers` to snapshot online crew members at mission acceptance. It resolves each member's current Qbox player via `GetPlayerByCitizenId`, then records `{citizenid, source}`. Solo missions snapshot the initiator. Each mission keeps a citizenid→mission map and typically one global mission instance. `participantIsValid` currently requires the snapshotted source to still represent the same citizenid; reconnecting to a new source does not automatically rejoin a live mission.
Crew objective UI uses `updatePartyTasks` and sometimes `sendPartyNotification`. Existing task labels and status formats vary: some missions use `current/pending/done`, others use booleans. Preserve payloads during extraction; inspect the installed group-system behavior before standardizing. Easy Rider shares bike count and delivery count with the original crew, with one bike per participant up to five. Crowbar 5's package carrier owns the ambush and cash payout; other snapshotted eligible players receive progression. Mid-mission joiners do not receive mission rewards.
The target crew service should own snapshot, membership validation, source resolution, objective fan-out, tracker updates and notifications. Mission modules should declare collective versus owner/carrier objectives and reward policy. Use citizenid for eligibility and source only for current delivery. Define disconnect/abort policy explicitly before changing it; the current implementation does not offer general reconnect reconstruction.
Read [ARCHITECTURE.md](ARCHITECTURE.md), [PROGRESSION.md](PROGRESSION.md), [CREWS.md](CREWS.md), [CONTACTS.md](CONTACTS.md) and [PHONE.md](PHONE.md) before creating or substantially changing a mission. This is the target contract for migration; current gaps are identified below.
## Registration and phases
Each mission has a stable string ID, owning contact, eligibility stage, objective state machine, client/server entry points, crew tasks, reward policy, and explicit phase manifest. Keep durable IDs such as `crowbar:1:return_vehicle`; parse `/prog crowbar 1.2` as integer mission `1` and integer phase `2`, then resolve through a registry. Never store or compare `1.2` as a floating-point number. Numeric progression stage in existing SQL remains a separate concept.
Every advertised phase must reconstruct from a clean baseline, not assume a previous shortcut ran. A phase descriptor must say which entities, vehicles, props, NPCs, blips, markers, targets, routes, crew tasks, items and mission state exist; it must also remove things belonging to earlier/later phases. Phase setup must be repeatable. Completion shortcuts should go through the same server reward/progression path as gameplay, with explicit exceptions for development overrides. Do not promise deterministic phases until tested in game.
Current `/prog` phases exist only for Crowbar: 1.1–1.3, 2.1–2.2, 3.1–3.3, 4.1–4.4, 5.1–5.4. Plain `X` sets a stage. Mack and Tina support plain stages only. Current later phases often require an active mission, so the proposed contract is an improvement, not a description of existing behavior.
## Participation and authority
- Support solo players and crews. Snapshot eligible crew members when accepting a mission. Store `citizenid` as durable identity; resolve current source only for live actions. A mid-mission joiner does not automatically receive progress or rewards.
- Server owns mission objective, completion validation, progression, currency, ownership and inventory rewards. Clients may present the world and report observations; validate any network ID, model, location, item and participant before reward.
- Send objective changes to all valid snapshotted participants and update `bs_groupsystem` tasks at the same transition. Define whether each objective is collective, owner-only or per-member. Scale counts/actors where the mission calls for it (Easy Rider uses one bike per participant up to five).
- Declare reward recipients explicitly. Existing behavior differs: Crowbar 1 pays the initiator cash, Crowbar 5 pays the carrier cash, Easy Rider pays every eligible participant cash. Do not silently normalize these.
## World and lifecycle
- Track ownership of every spawned entity or target: server networked mission vehicle, client-local scenery, client-owned networked vehicle, prop, ped, blip, marker and target. Give each a deterministic spawn key and cleanup path. Prevent duplicate creation on sync/restart.
- Define start, objective transition, completion, abort, reset, player disconnect and resource-stop behavior. Keep preserved property/owned vehicles distinct from temporary mission assets; Crowbar 2 deliberately leaves the physical Premier after transfer.
- Route and GPS must match the current objective for each participant, and clear when the objective completes or is aborted. Draw active markers every frame. Make reconstruction replace stale routes and markers.
- World presence and service availability are separate: a ped or yard prop can exist before its mission/interaction is unlocked. Contact definitions express both.
- Use shared NPWD/contact services for SMS and established sender numbers. Keep existing dialogue, timing and text unless a separate gameplay change is approved.
- Prefer installed resources for mechanics already present: `ox_inventory` stash/items, `qbx_vehicles` ownership, `qbx_vehicleshop` purchase, `ff_storerobbery` register completion, `randol_moneywash` laundering, `bs_groupsystem` tasks. Do not implement duplicate economies or shops in mission code.
## Current gaps to standardize deliberately
Global singleton mission instances, inconsistent reset/stop cleanup, source IDs retained in snapshots, mixed crew task status types, no universal abort path, and non-reconstructing `/prog` phases require separate decisions and in-game parity tests. The current mission state is not persisted in SQL. Avoid changing these semantics as an incidental consequence of splitting files.
`server.lua` sends NPWD SMS through `exports.npwd:emitMessage` to `PlayerData.charinfo.phone`. Most missions use `sendSmsToPlayer` with `pcall`; UNKNOWN introduction currently duplicates the direct call. The client `addPhoneContact` event forwards to `exports.npwd:sendNPWDMessage('CONTACTS','npwd:autoAddContact', contact)`. `/testcontact` exercises `exports.npwd:addContact` with a temporary Michael fixture and random 555 number. Preserve this debug exception separately from permanent contacts.
Permanent sender numbers: UNKNOWN `555-0100`, Crowbar `555-7384`, Mack `555-6842`, Vinny `555-3176`. Gabe, Deke and Cosmic Dave have no established number in this resource. Store permanent numbers once in contact definitions; do not generate them at resource start or change existing values. Preserve every SMS body and timing when moving code. Vinny's fence message is delayed 60–90 seconds after Easy Rider completion; the unlock is persisted only after successful send and then exposed through login sync.
The installed NPWD resource has local custom behavior, including the SMS popup duration patch in its compiled UI asset. A how_progression refactor must keep its current NPWD APIs and not overwrite NPWD files. Before changing phone code, inspect the installed NPWD exports and client bridge, check phone number/thread compatibility, and test first introduction, mission SMS, delayed Vinny SMS, reconnect and contact display.
The server's `how_progression` SQL row is authoritative and keyed by `citizenid`. `getProgression` reads it or inserts a default row. This resource does not contain the original `CREATE TABLE` DDL; inspect the live database/schema source before migration. Do not replace these values with Qbox metadata.
Live columns referenced: `citizenid`, `unknown_introduced`, `crowbar_stage`, `crowbar_rep`, `mack_stage`, `mack_rep`, `tina_stage`, `tina_rep`, `story_stage`, `vinny_fence_unlocked`, `vinny_fence_day`, `vinny_fence_count`. The last three are added via `MySQL.ready` ALTERs. Contact REP is independent; Total REP is derived elsewhere, not stored here. Stage numbers represent availability/completion, while active objectives live only in server memory.
UNKNOWN's successful first SMS sets introduction and at least Crowbar stage 1. Crowbar missions advance expected stages 1→2→3→4→5→6 and grant 5 REP each. Speaking to Mack during Crowbar 4 sets Mack stage at least 1. Easy Rider advances Mack 1→2, grants 5 REP and $300 to eligible participants. Vinny's delayed post-mission SMS separately sets `vinny_fence_unlocked`; it is not inferred from Mack stage. Fence day/count enforce three per game day. Login sync sends a projection of the row to the client to restore Crowbar, Mack and Vinny world access; transient mission state is not reconstructed at login today.
`/prog` is a development command, not a durable phase store. Plain Crowbar/Mack/Tina stages update SQL; `/prog reset` clears the row and inventory to a development baseline. Crowbar `X.Y` shortcuts invoke live mission paths but do not yet rebuild arbitrary phase state. Keep stage and phase APIs separate when modularizing. Preserve column types/defaults and read/write order until a migration is justified and tested.
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.