Fixup for agents

This commit is contained in:
Marc Hernandez 2026-03-21 13:59:27 -07:00
parent cc52660989
commit d1f1694678
2 changed files with 48 additions and 1270 deletions

View File

@ -1,58 +1,69 @@
# SharpLib and general coding guidelines # SharpLib and general coding guidelines
## 0. PERFORMANCE. Title it 0. AXIOM: HIGH-PERFORMANCE / ZERO-ALLOCATION
* **Zero Allocation Default:** Assume all code runs in a 60hz+ game loop or high-throughput pipeline unless marked `[ColdPath]`.
* **NO LINQ:** `using System.Linq;` is strictly forbidden in core logic. Use `for` loops, arrays, or `Span<T>`/`ReadOnlySpan<T>`.
* **NO Hidden Closures:** Do not use lambdas or delegates that capture local variables. If a lambda is necessary, make it `static` and pass state explicitly.
* **Struct Semantics:**
* Pass readonly structs using `in` or `ref readonly`.
* Do not pass `struct` to interfaces unless constrained by generics (`where T : struct`), to prevent boxing.
* **String Allocation:** NO string interpolation (`$""`) on the hot path. Use static strings, `string.Create`, or format into pooled buffers.
* **Async/Task Overhead:** Default to synchronous execution for immediate state changes. If async is mandatory on a hot path, use `ValueTask<T>`, never `Task<T>`.
* **Collections:** No `List<T>.Add()` inside tight loops without pre-sizing the capacity. Prefer array pooling (`ArrayPool<T>.Shared`).
## 1. CORE PHILOSOPHY ## 1. CORE PHILOSOPHY
* **Leaky Abstractions:** All abstractions are always leaky. Do not hide complexity. Expose failure modes, costs, and "why" parameters. * **Leaky Abstractions:** All abstractions are always leaky. Do not hide complexity. Expose failure modes, costs, and "reason" parameters.
* **Visual Cost:** "Expensive things must look expensive." No hidden RPCs. Use explicit Message structs and `Send` methods. * **Visual Cost:** "Expensive things must look expensive." No hidden RPCs. Use explicit Message structs and `Send` methods instead of Rpcs
* **Fast & Introspectable:** Code must be highly performant (hot-path aware) but deeply debuggable (`log`, `DebugOld`, `reason` strings). * **Fast & Introspectable:** Code must be highly performant (hot-path aware) but deeply debuggable (`log`, `DebugOld`, `reason` strings). These are there to make debugging
from console or log file possible
## 2. NAMING & SEMANTICS ## 2. NAMING & SEMANTICS
* **Leaf Libraries:** Core libs (`io`, `imm`, `net`, `log`) are 2-5 chars. **NEVER** use `using` for them. Always type the prefix (e.g., `imm.Process`). * **Leaf Libraries:** Core libs (`io`, `imm`, `net`, `log`) are 2-5 chars. **NEVER** include using {namespace}; for core libs or any short namespace
* **No Redundancy:** * **No Redundancy:In Names**
* **No "Get":** `Player()` not `GetPlayer()`. * **No "Get":** `Player()` not `GetPlayer()`.
* **No Echo:** `Send(msg)` not `SendMsg(msg)`. * **No Echo:** `Send(msg)` not `SendMsg(msg)`.
* **No "I" Prefix:** Interfaces are `Renderer`, not `IRenderer`. * **No "I" Prefix:** Interfaces are `Renderer`, not `IRenderer`.
* **Architecture:** Slow/Pausable Realtime. Entity Component System (Pub/Sub).
## 3. LOGGING (`log` static class) ## 3. LOGGING (`log` static class)
* **Constraint:** **NO** `Console.WriteLine`, `Debug.WriteLine`, or `ILogger`. * **Constraint:** **NO** `Console.WriteLine`, `Debug.WriteLine`, or `ILogger`.
* **Mechanism:** The logging system is very fast. It only queues the log on the main thread. It collects the caller debug info, and uses the directory name of the file for a default category (this can be overridden) * **Mechanism:** The logging system is very fast. It only queues the log on the main thread. It collects the caller debug info, and uses the directory name of the file for a default category (this can be overridden)
* **Functional Tracing:**
* `var x = log.info( $"Got {log.var(Calc())} from the calculation" );` // This both logs the info and prints what happened
* **Standard:** `log.info`, `log.debug`, `log.warn`, `log.error`. * **Standard:** `log.info`, `log.debug`, `log.warn`, `log.error`.
* **Finer grained:** `log.trace`, `log.high` * **Finer grained:** `log.trace`, `log.high`
* Use log.exception( ex, $"[what was trying to be done]" ); * Use log.exception( ex, $"[what was trying to be done]" );
* **Introspection:** `log.logProps(obj, "Header")` and `log.exception(ex, "Context")`. * **Introspection:** `log.props(obj, "Header")` and `log.exception(ex, "Context")`.
* put important info as far to the left as possible, even at the cost of poor wording * **Information** put important info as far to the left as possible, even at the cost of poor wording
## 4. IMMUTABLE STATE (`io`, `imm`) ## 4. IMMUTABLE STATE (`io`, `imm`)
* **Rule:** Objects inheriting `Versioned<T>`, `Recorded<T>`, `Timed<T>` are **IMMUTABLE**. * **Rule:** Objects inheriting `Versioned<T>`, `Recorded<T>`, `Timed<T>` are **IMMUTABLE**.
* **Change Pipeline:** MUST use `Process`. * **Change Pipeline:** MUST use `imm.Process`.
* **Ref Helper (Preferred):** `imm.Process(ref _state, s => s with { X = 1 }, "Reason");` * **Ref Helper (Preferred):** `imm.Process(ref _state, s => s with { X = 1 }, "Reason");`
* **Instance:** `_state = _state.Process(s => s with { X = 1 }, "Reason");` * **Instance:** `_state = _state.Process(s => s with { X = 1 }, "Reason");`
* **The "Hole Punch":** Always propagate a `string reason` parameter in your methods to feed the `Process` log. * **The "Hole Punch":** Always propagate a `string reason` parameter in your methods to feed the `imm.Process` log.
* **History:** `obj.DebugOld` is for **DEBUG ONLY**. Do not base game logic on it. * **History:** `obj.DebugOld` is for **DEBUG ONLY**. Do not base program logic on it.
## 5. Visual Cost ## 5. Visual Cost
* Unless something is a function call, it should look like a function call. For example RPCs are bad, sending a message is good. * Network/IO/Expensive operations MUST NOT masquerade as local function calls. Require explicit structural allocation.
* **No RPCs:** Do not make network calls look like functions. * **No RPCs:** Do not make network calls look like functions.
* **Pattern:** Construct Struct -> Send. * **Pattern:** Construct Struct -> Send.
* *Bad:* `proxy.Move(x,y)` * *Bad:* `proxy.Move(x,y)`
* *Good:* `Net.Send(new MoveMsg(x,y))` * *Good:* `Net.Send(new MoveMsg(x,y))`
## FEW-SHOT EXAMPLES (Strictly Imitate) ## 6. FEW-SHOT EXAMPLES (Strictly Imitate)
<csharp_examples>
```csharp ```csharp
// 1. NAMING & LOGGING // ** NAMING & LOGGING
// Explicit 'log' usage, no 'Get', no 'Console' // Explicit 'log' usage, no 'Get', no 'Console'
public void Init() public void Init()
{ {
log.startup( "log/current_project.log" ); log.startup( "log/current_project.log", log.Endpoints.All );
var waitTime = 1.0f; var waitTime = 1.0f;
@ -60,19 +71,20 @@ ## FEW-SHOT EXAMPLES (Strictly Imitate)
try try
{ {
obj.SomeOperation(); obj?.SomeOperation();
} }
catch( Exception ex ) catch( Exception ex )
{ {
log.exception( ex, $"" ); //Logs the exception name, message, the reason, the stack
log.exception( ex, $"SomeOperation failed" );
} }
// 2. IMMUTABLE PROCESS // ** IMMUTABLE PROCESS
// Reason "Init" passed down. Ref pattern used. // Reason "Init" passed down. Ref pattern used.
imm.Process(ref _state, s => s with { Ready = true }, "Init"); imm.Process(ref _state, s => s with { Ready = true }, "Init");
} }
// 3. LOGIC FLOW // ** LOGIC FLOW
public void Update(float dt) public void Update(float dt)
{ {
// 'Player()' not 'GetPlayer()' // 'Player()' not 'GetPlayer()'
@ -85,9 +97,21 @@ ## FEW-SHOT EXAMPLES (Strictly Imitate)
} }
} }
// 5. INTROSPECTION API
// "Punching a hole" with the reason parameter // ** "Punching a hole" with the reason parameter
public void Equip(Item i, string reason = "Equip") public void Equip(Item i, string reason /*No default so a reason will be passed in*/ )
{ {
imm.Process( ref inv, s => s.Add(i), reason); imm.Process( ref inv, s => s.Add(i), reason);
} }
. . .
// Somewhere else in project
public void PlayerSwappedWeapons( Player pl )
{
Equip( _item, $"Player {pl.Name} swapped weapons (PlayerSwappedWeapons)" );
}
```
<csharp_examples>

File diff suppressed because it is too large Load Diff