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
34 changes: 34 additions & 0 deletions evals/RESULTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,37 @@ Not changed: whether a server toast sent at `onPlayerReady` is queued or lost wh
WebUI is still coming up (the agent waited 3 s by analogy with the identity guide); whether
`tostring(integer id)` equals the event's string id is asserted in the seat card from the host's
`ToString()` and not measured with a real vehicle.

## RP round, 2026-09-17 03:50–04:40: five agents write a roleplay server's basics on 0.1.2

Five subagents, `@open2077/mcp@0.1.2` only, one resource each, with a shared contract for the
cross-resource exports (`rp-round/PROMPT-COMMON.md`): `rp_economy` (wallet, payday, exports),
`rp_jobs` (five jobs, courier mission with a spawned car, GPS waypoints and paid deliveries),
`rp_shop` (consumables, weapons through the `open77_weapons` relay, vehicles, resale), `rp_chat`
(`/me` `/do` `/ooc` `/w` `/dice` `/showid` with proximity audiences), `rp_medic` (paid heal /
revive gated by the job, `/911`, `/medic`). All five validated OK first pass, compile with the
server's own Lua (`--lint`), start on the eval server (op77 3ca1ca66, client 76) and answer every
command from the console; `rp-round/rp_selftest` exercises the exports across VMs (8/8). The
player-side paths wait for a client (the owner's was up all night). 40–49 MCP calls per agent.

What the round found:

- **Command names collide silently**: `/heal` belongs to `open77_admin` and `/revive` to
freeroam; a name registered by two resources is served by the first, with no error. The medic
verbs became `/soin` / `/reanimer`. Nothing in the MCP lists the names the platform already
owns — `open77_validate` should (from the resources the server dir loads).
- `open77_events` said `chat:ready` had no documented payload (events.json predated the chat.md
row; regenerated, base #28). `Open77.time.monotonic` / `runtime.luaVersion` / `state.clear`
carried ~50 unrelated reasons (one-line table members read with the indentation reader;
fixed). The server-exports guide contradicted the `TriggerEvent` card (VM-local vs host bus;
fixed). Six examples passed a host-event string id to `chat.send` (fixed).
- `open77_data` never named its `catalogue` argument (two agents lost a call); it does now, and
says which vehicle records are player-spawnable.
- No server card for the `exports` registration global (guide prose only); `players.get().ready`
and `players.all()` element type undocumented (integers, measured); the `parameters` shape of
chat suggestions was a FiveM guess (now documented); `players.stats.read` /
`players.life.read` say "enforced by: client" while gating server natives.
- `since` moved on thirty client cards when the surface history was rebuilt after a reader
change: main had been fast-forwarded onto a branch that merged main, and the `--first-parent`
walk no longer resolved release dates to the commits the builds were cut from. Release commits
are pinned in the history file now (base #28).
77 changes: 77 additions & 0 deletions evals/rp-round/PROMPT-COMMON.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Common rules for the RP resource round (read fully before starting)

You are writing ONE Lua resource for the Open77 platform (a Cyberpunk 2077 multiplayer
framework, FiveM-like: manifest `open77.lua`, `server_script` / `client_script`, natives under
`Open77.*` plus FiveM-shaped globals). Your ONLY source of truth is the Open77 Devkit MCP,
driven through this harness (one call per invocation, run from any directory):

node "C:/Users/Wodman/AppData/Local/Temp/claude/C--Games-cyberm/47b3b407-f916-4dcf-a69e-303889c762ee/scratchpad/devkit-eval2/mcp-call-012-76.mjs" "C:/Games/cyberm/rp-scripts" <tool> '<json args>'
node ... "C:/Games/cyberm/rp-scripts" list # the tool list
node ... "C:/Games/cyberm/rp-scripts" open77_search '{"query":"give money to a player"}'
node ... "C:/Games/cyberm/rp-scripts" open77_api '{"name":"Open77.players.name"}'
node ... "C:/Games/cyberm/rp-scripts" open77_guide '{"slug":"server-exports"}'
node ... "C:/Games/cyberm/rp-scripts" open77_validate '{"resource":"C:/Games/cyberm/rp-scripts/<your resource>"}'

Pass JSON args on the command line in single quotes; for arguments containing quotes, write
the JSON to a file and pass `@C:/path/args.json` instead. Each call takes 5-15 s (npx start).
The MCP answers for server build **2.31.13+op77.76**; that is the build the resource runs on.

## Hard rules

1. Do NOT read any file under `C:\Games\cyberm` other than your own resource directory. No
web search. Do not start, stop or talk to any server or game process. Do not run git.
2. Start with `list`, then `open77_skill` if present (or `open77_guide README`), then search.
Read the card (`open77_api`) of EVERY native you call: permission, reasons, since, runtime
side. Declare in the manifest exactly the permissions the cards require. A native the MCP
says is unavailable on op77.76 must not be used.
3. Server-authoritative design: the server decides money, jobs, spawns; a client only renders
and requests. Every native that returns `nil, reason` / `false, reason` is checked and the
player is told why in chat (French, short). Player-facing text in **French**, code comments
in English, no BOM, LF line endings, 4-space indent, Lua 5.4.
4. Persistence: use the KVP API the MCP documents (`Open77.kvp` or the server equivalent),
keyed by the player's stable identifier (find the native that gives a stable identity, not
the session id). Nothing may crash on a missing key or a departed player.
5. Host lifecycle events (`onPlayerReady`, `onPlayerDisconnected`, ...) deliver every argument
as a STRING; convert with `tonumber` before handing an id to a native that wants a number
(`Open77.chat.send` refuses strings). Inside `RegisterCommand` / `RegisterNetEvent`
handlers, `source` is already a number (0 = server console: refuse politely).
6. `Open77.chat.*` and `TriggerEvent` are refused with `resource_preparing` at the top level of
a script: put them in `AddEventHandler("onResourceStart", ...)` (check the name is your
own), commands or event handlers. Publish chat command suggestions on `chat:ready`
(`RegisterNetEvent`, `source` is the player, no arguments) and once from `onResourceStart`
with `-1`.
7. Sandbox: no `os`, `io`, `require`, `load`, `debug`. Wall clock: `Open77.time.unix()`;
elapsed: `Open77.time.monotonic()`; `math.random` is available.
8. Cross-resource calls: server exports, called synchronously as
`exports.<resource>:<name>(...)` (read `open77_guide server-exports` for the exact form,
the promise/await rules and `GetInvokingResource`). Declare `dependency "<resource>"` in
the manifest for every resource whose exports you call. If the export is missing at run
time, degrade gracefully (message in chat), never crash.
9. Run `open77_validate` on your resource until it reports OK with 0 errors; keep warnings
only when you can explain them. Then break it once on purpose (wrong-side native, unknown
native, missing permission) to confirm the validator catches it, and restore.
10. Final deliverable, written under `C:\Games\cyberm\rp-scripts\<resource>\`:
- `open77.lua`, `server/main.lua`, optionally `client/main.lua`, optionally `README.md`
(French: what each command does, how to test it in 2 minutes).
Then hand back a report with: (a) the command list and what each does, (b) every MCP
call in order (tool + args + one word on usefulness), (c) where the MCP was wrong,
ambiguous, missing or made you guess, (d) the final `open77_validate` output verbatim,
(e) the final file contents. Report only; do not test on a server.

## Shared contracts between the resources of this round (respect the names exactly)

- `rp_economy` publishes server exports:
- `getBalance(playerId) -> integer` (0 when unknown)
- `add(playerId, amount, reason) -> newBalance | nil, reason`
- `remove(playerId, amount, reason) -> newBalance | nil, "insufficient_funds" | nil, reason`
and raises the host-wide event `rp_economy:changed` (playerId, newBalance, delta, reason)
through `TriggerEvent` after every change.
- `rp_jobs` publishes server exports:
- `getJob(playerId) -> jobName | nil`
- `hasJob(playerId, jobName) -> boolean`
and raises `rp_jobs:changed` (playerId, jobName | nil) on every change. Job names are
lower-case ASCII: `livreur`, `taxi`, `mecano`, `medecin`, `police`.
- Every command name is lower-case, without a prefix, and must not collide with these,
which other resources of the round own: `money`, `pay`, `givemoney`, `payday` (economy);
`jobs`, `job`, `mission`, `stopmission` (jobs); `shop`, `buy`, `sell` (shop); `me`, `do`,
`ooc`, `w`, `showid`, `dice` (chat); `heal`, `revive`, `911`, `medic` (medic).
71 changes: 71 additions & 0 deletions evals/rp-round/rp_chat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# rp_chat — commandes de chat roleplay

Ressource **serveur uniquement** pour Open77 (build `2.31.13+op77.76`). Elle ajoute les
commandes de chat classiques d'un serveur RP : le serveur décide de l'audience (proximité),
lit les noms, les emplois et les soldes, et pousse les lignes par `Open77.chat`.

## Commandes

| Commande | Effet | Audience |
|---|---|---|
| `/me <action>` | `* <Nom> <action>` en violet | tous les joueurs à **30 m** (vous compris) |
| `/do <description>` | `** <description> ((<Nom>))` en violet clair | 30 m |
| `/ooc <texte>` | `(( OOC ) <Nom>: <texte>` en gris | **tout le serveur** |
| `/w <playerId> <texte>` | murmure : la cible voit `(murmure) <Nom> : <texte>`, vous voyez `(murmure à <Cible>) <Nom> : <texte>`, en gris sombre | vous et la cible, qui doit être à **10 m** |
| `/dice [faces]` | `<Nom> lance un dé (6) : 4` en doré ; 6 faces par défaut, 2 à 1000 | 30 m |
| `/showid [playerId]` | carte d'identité : nom, emploi, et **votre solde uniquement sur votre propre carte** | vous seul ; la cible doit être à **5 m** |

Les `playerId` sont les identifiants de session (`/id` dans le chat). Le nom vient de
`Open77.players.name`. Toute commande lancée depuis la console serveur est refusée
poliment (journal seulement) : elles exigent un personnage dans le monde.

Chaque refus est expliqué au joueur en français : joueur introuvable, trop loin
(distance affichée), position inconnue, message vide ou trop long (512 octets), nombre
de faces invalide, etc.

## Sources des données

- **Audience de proximité** : `Open77.players.nearby(joueur, rayon, { includeSelf = true })`
(documenté par le MCP, disponible depuis op77.67). Il respecte le bucket de routage du
joueur. Si la position du joueur est inconnue (pas encore dans le monde), le message
n'est pas envoyé et le joueur en est informé — il n'y a pas de repli vers « tout le
monde », qui serait un faux positif en RP.
- **Distance** (`/w`, `/showid`) : `Open77.players.distance(a, b)`, en 3D.
- **Emploi** : `exports.rp_jobs:getJob(playerId)` ; `sans emploi` quand l'export manque,
échoue ou rend `nil`.
- **Solde** : `exports.rp_economy:getBalance(playerId)` ; `indisponible` quand l'export
manque ou échoue. Les exports synchrones lèvent une erreur en cas d'échec : chaque
appel est enveloppé dans `pcall`, rien ne plante.
- **Carte d'identité** : envoyée en **notification** (`Open77.notifications.send`) quand
la native existe **et** que la ressource `open77_notifications` est `running`
(`Open77.resource.state`) ; sinon, ou si l'envoi échoue, en lignes de chat (une par
tick pour garder l'ordre). Le manifeste ne déclare pas `open77_notifications` en
dépendance dure : sans le paquet, les commandes de chat restent disponibles et la
carte s'affiche dans le chat.

## Manifeste

- `permissions { "network.events" }` : exigé par `RegisterNetEvent("chat:ready")` et
`Open77.notifications.send`.
- `dependency "rp_jobs"`, `dependency "rp_economy"` : les deux ressources dont les
exports sont appelés (ordre de démarrage ; l'absence d'un export reste gérée).
- Aucune persistance : cette ressource ne stocke rien, le KVP n'est pas nécessaire.

## Tester en deux minutes

1. Déposer `rp_chat/` dans la racine des ressources, avec `rp_jobs` et `rp_economy`.
Démarrer le serveur ; le journal affiche
`[rp_chat] started: /me /do /ooc /w /dice /showid (notifications: toast|chat fallback)`.
2. Se connecter avec **deux clients** (A et B), noter leurs `/id`.
3. A et B côte à côte : `/me regarde autour de lui` → les deux voient
`* A regarde autour de lui`. `/do Il pleut.` → `** Il pleut. ((A))`.
4. B s'éloigne à plus de 30 m : `/me tousse` chez A n'atteint plus B (A le voit toujours).
5. `/ooc salut` → tout le serveur voit `(( OOC ) A: salut` en gris.
6. À moins de 10 m : `/w <idB> psst` → B voit `(murmure) A : psst`, A voit
`(murmure à B) A : psst`. À plus de 10 m : `Trop loin : B est à 14 m (10 m maximum).`
7. `/dice` → `A lance un dé (6) : n` ; `/dice 20` → `(20)` ; `/dice 1` → refus.
8. `/showid` → carte avec nom, emploi et solde. `/showid <idB>` à moins de 5 m → carte de
B **sans** solde ; à plus de 5 m → refus avec la distance.
9. Depuis la console serveur, `me test` → rien dans le chat, une ligne de refus dans le
journal.
10. Ouvrir le chat et taper `/` : les six commandes apparaissent dans la complétion.
12 changes: 12 additions & 0 deletions evals/rp-round/rp_chat/open77.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "rp_chat"
version "1.0.0"
auto_start true

-- network.events: RegisterNetEvent("chat:ready") and Open77.notifications.send
permissions { "network.events" }

-- Server exports this resource calls (exports.rp_jobs:getJob, exports.rp_economy:getBalance)
dependency "rp_jobs"
dependency "rp_economy"

server_script "server/main.lua"
Loading
Loading