| name | pcx-coding-discipline |
| description | Workflow discipline for developing Enma (.em) and AngelScript (.as) scripts on Perception.cx. Derived from Karpathy principles — think before coding, simplicity first, surgical changes, goal-driven execution — rewritten for cheat development realities: stale offsets, silent failed reads, detection surface. Always active when writing or editing PCX scripts.
|
| license | MIT |
PCX Coding Discipline — How to Write Scripts, Not What They Look Like
Workflow discipline for developing Enma (.em) and AngelScript (.as) scripts on Perception.cx. Derived from the four Karpathy principles — think before coding, simplicity first, surgical changes, goal-driven execution — and rewritten for the realities of cheat development: stale offsets, silent failed reads, detection surface, and overlays you debug by looking at them.
Always active when writing or editing PCX scripts. This is the process layer. The game-cheat-guidelines skill is the code-shape layer (uint64 addresses, null guards, render separation). Load both: this one tells you how to work, that one tells you what the code must look like.
Prerequisite: Read the relevant doc before writing any API call — see skill://game-hacking-pcx for the file-by-file index.
Trigger
Writing or editing any .em / .as script, adding a cheat feature, refactoring a script, fixing a broken overlay, deciding how much to build, or judging whether a script is "done."
1. Think Before You Touch the Editor
Name the target, the source of every offset, and the tradeoff you're making — out loud — before you write a line.
The single most expensive habit in cheat development is writing code against assumptions. A wrong offset doesn't throw; it reads garbage and your ESP draws at (0, 0). Before implementing:
- State the target. Game, engine, module. "Apex / Source (r5) /
r5apex.exe."
- State where each offset comes from. Sig scan, SDK header, or hardcode — and say which. If you're guessing a struct field, write
// UNVERIFIED next to it.
- Surface the tradeoff the user didn't ask about. Read-only ESP is invisible; a memory write for aimbot is a detection surface. Per-frame reads are simple but couple render to read latency. Say which you're choosing and why.
- If the doc is ambiguous or the API is permission-gated, stop and read it. Do not invent
draw_esp() or assume draw_circle takes a fill flag. Open docs/perception/render-api.md.
Before: "I'll write an ESP overlay."
*invents function names, assumes int32 offsets, no W2S behind-camera check*
After: "Target: Apex / Source (r5). Entity list via sig (UNVERIFIED layout, r5sdk season 21).
Read-only ESP, per-frame W2S — accepting read/render coupling for v1 simplicity.
Reading render-api.md + lifecycle-and-routines.md before writing."
Why: Confusion hidden behind plausible code costs hours. Confusion stated up front costs one sentence and gets corrected before it compiles into a silent bug.
2. The Simplest Cheat That Works
Build the minimum feature that satisfies the ask. Nothing speculative.
Cheat scripts rot into 2000-line monoliths because every feature arrives with prediction, smoothing, themes, and a config framework nobody requested. Climb down the ladder:
- "Highlight enemies" is a box, not a skeleton-ESP-with-bones-and-LOD. Ship the box. Add bones when asked.
- An aimbot the user described as "snap to head" doesn't need velocity prediction. Don't add it.
- No config system for a value that never changes. A fixed enemy color is
color(255,0,0,255), not a JSON-loaded theme engine.
- No abstraction over the proc API.
p.ru64(...) is the interface. Wrapping it in a MemoryManager class buys nothing.
- No "feature manager" framework for three features. Three registered routines is the framework.
class IFeature { void update(); void render(); }
class FeatureRegistry { array<IFeature@> features; ... }
class EspFeature : IFeature { }
void on_update(int64 data) { }
void on_render(int64 data) { }
Why: Every speculative line is a line someone debugs at 3am after a patch. The lazy version ships today and is trivially extended when a real second requirement shows up — which is the only honest signal that the abstraction was needed.
3. Surgical Edits — One Feature, One Diff
When changing a script, touch only the feature you're changing. Clean up only the mess your change makes.
Perception scripts are built for hot reload precisely so you can change one file without disturbing the rest. Honor that:
- Editing ESP color? Edit
esp.em. Do not reformat menu.em, rename globals in globals.em, or "tidy" main.em while you're in there.
- Match the module's existing style — naming, the per-feature file split, the order of routine registration. A second convention beside the first is worse than the style you'd have picked.
- If your change orphans a global or import, remove it. If you spot pre-existing dead code unrelated to your change, mention it — don't delete it.
- Don't churn working offsets. A sig that still hits and resolves to valid data is not your problem today.
Task: "the enemy boxes are the wrong color"
WRONG diff: esp.em (color) + globals.em (renamed g_col → g_enemyColor)
+ menu.em (reordered widgets) + main.em (reformatted)
RIGHT diff: esp.em (color)
Why: Every file you touch is a file that can break and a file the next reader has to diff. A four-file diff to change one color hides the actual change and risks the three features you didn't mean to touch.
4. Done Means It Works on the Target
Define success as something you can see on the live game, then loop until you see it. Compiling is not done.
A script that compiles has proven nothing about whether the offsets are right, the W2S matches the engine, or the overlay aligns. Set a concrete bar and verify against it:
- Write the success criteria before coding, as observable facts: "boxes track enemies at any distance, nothing draws behind the camera, count matches the scoreboard."
- The overlay is your debugger. When something's off, draw the raw W2S coordinates and
print the entity count — don't guess.
- Loop: compile → load → look at the screen → compare to the criteria → fix → reload. Repeat until every criterion holds.
- When the IDB, the SDK, and the live read disagree, trust the live read (see
game-cheat-guidelines #12). The SDK may be from an older season.
Success criteria for "enemy ESP":
[ ] A box appears on every enemy entity (count == live enemy count)
[ ] Boxes track movement smoothly, no stutter
[ ] No box renders when the entity is behind the camera (W2S w > 0)
[ ] No box at (0,0) — that means a null read slipped a guard
[ ] Boxes scale with distance (far enemies = smaller boxes)
Why: "It compiles" and "it works" are different claims, and only the second one is the deliverable. A success checklist turns a vague "make ESP" into a loop you can run yourself without asking the user whether it's right.
5. Deletion Before Addition
Try removing code before writing new code. The shortest script that works is the one with the fewest lines to break after a patch.
When a feature request arrives, check what already exists first:
- Can you delete a workaround instead of adding a second one? Two workarounds for the same stale offset is a sign one should die.
- Can you inline a wrapper? A
ReadEntity() function that calls p.ru64() once with no validation adds a name, not value. Inline it.
- Can you merge two features into one routine? If
on_update_esp and on_update_radar both walk the same entity list, one walk and two draw calls in on_render is fewer lines and fewer reads.
- Before adding a class, count its callers. One caller = inline. Two = maybe. Three = extract, not before.
uint64 ReadEntityBase(proc_t@ p, uint64 list, int idx) {
return p.ru64(list + idx * 0x20);
}
uint64 ent = p.ru64(entity_list + i * 0x20);
Why: Every line in a cheat script is a line you re-validate after a game patch. 80 lines is 80 potential breakpoints. 40 lines is half the post-patch work.
6. Question the Requirement
Ship the minimum, then challenge the rest — in the same response, not a separate conversation.
When the ask is vague or ambitious ("make a full ESP with health bars, distance, snaplines, team colors, and a config panel"):
- Build the core — boxes on enemies, W2S, null guards.
- Ship it working.
- In the same response: "Done: box ESP with W2S + null guards. Health bars and snaplines are 10 lines each when you want them. Team colors need a second read per entity — add when the base ESP is confirmed working. Config panel is overhead for 3 settings —
bool globals + a sidebar checkbox cover it."
Never stall on an answer you can default. Never build five features to avoid the conversation about whether three of them matter.
Pattern: [working code] → skipped: [X]. add when [Y].
7. Mark Deliberate Shortcuts
Every deliberate simplification gets a // defer: comment naming its ceiling and the trigger to revisit.
// UNVERIFIED marks offset confidence. // defer: marks design shortcuts — places where you chose the simple path and know the ceiling.
void on_update(int64 data) { ... }
color enemy_col = color(255, 0, 0, 255);
proc_t@ g_proc;
Format: // defer: <what was simplified>, <when to revisit>
A // defer: with no trigger is a shortcut that rots silently. Always name the trigger.
Not deferred: pointer validation, w > 0 checks, uint64 for addresses, f suffix on floats. Those are the floor, not shortcuts.
8. One Self-Check Per Non-Trivial Feature
You can't unit test against a live game, but non-trivial logic leaves one sanity print behind.
Cheat scripts run against a live target — no mock framework, no test harness. But logic bugs (wrong struct offset math, bad matrix indexing, off-by-one in entity iteration) can be caught with a visible sanity check:
- Entity count print:
print("entities: " + g_positions.length()); in on_update. If it reads 0 or 9999, something's wrong before you even look at the overlay.
- Address range check:
if (addr < 0x10000 || addr > 0x7FFFFFFFFFFF) print("suspect addr: " + addr); — catches sign-extension and null-deref-adjacent reads.
- W2S validation: draw the raw screen coords as text before drawing boxes. If they cluster at (0,0), a null read slipped.
- One
print() per feature, gated behind a debug flag. Not a logging framework — one line.
if (g_debug) print("[esp] ents=" + ents.length() + " visible=" + drawn);
Why: The laziest debugger that catches real bugs. One print per feature is near-zero overhead. A logging framework for three features is debt you don't need.
Summary
| # | Principle | In PCX terms |
|---|
| 1 | Think Before Coding | Name target, offset source, and tradeoff before the first line |
| 2 | Simplicity First | Ship the box, not the framework — no speculative features |
| 3 | Surgical Changes | One feature, one diff; clean only your own orphans |
| 4 | Goal-Driven Execution | Done = visible success criteria met on the live target, not "compiles" |
| 5 | Deletion Before Addition | Try removing/inlining before writing new code |
| 6 | Question the Requirement | Ship the minimum, challenge the rest in the same response |
| 7 | Mark Deliberate Shortcuts | // defer: <ceiling>, <trigger> for design shortcuts |
| 8 | One Self-Check Per Feature | One print() per non-trivial feature, gated behind g_debug |