Last active
November 7, 2025 18:53
-
-
Save keithharvey/a7d2375c0988e0ad7021002a3b8ad268 to your computer and use it in GitHub Desktop.
BAR Modules?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # BAR “Policies” Proposal — why modGadget isn’t quite the right factoring (but the idea is right) | |
| **Problem we’re solving** | |
| * We want radical configuration (game modes/behaviors) without spaghetti. | |
| * Today, each gadget/widget decides things on its own; UI and gameplay drift apart; mod options are mostly one-shot. | |
| **Goal** | |
| * One clear place that *decides* gameplay rules. | |
| * A single contract the UI can *read* and reflect. | |
| * A predictable way for user actions to become *commands* in synced code. | |
| --- | |
| ## Core idea (modules, unidirectional) | |
| Modules own a particular game behavior. Within modules, we can focus on making our code reusable by contributors in a way that is maintainable and reusable. | |
| Reads and writes are different things: | |
| * **UI (widgets)**: *read* policies and render; on explicit user actions they send a **command** up (no direct state changes). | |
| * **Synced “domain modules” (gadgets)**: *own* the rules and execute commands; they *publish* policy results for the UI to read. | |
| * **Mod options / game marketplace**: choose which modules/policies are active; they don’t mutate at runtime. | |
| ```text | |
| UI (reads policies) ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─► | |
| ▲ (no direct writes) | |
| │ user command (RegisterGlobal/SendToSynced) | |
| │ | |
| Synced domain module (actions + policies) ──► publishes PolicyResults | |
| ``` | |
| This is essentially CQRS: **Commands** (writes) execute in one place; **Queries** (reads) are policy snapshots the UI consumes. | |
| --- | |
| ## Why not a single “modGadget” entry point? | |
| Your modGadget concept (authorized messages → execute whitelisted functions) is a good *capability* but the wrong *home base* for long-term factoring: | |
| * It re-introduces bi-directional, ad-hoc flows (hard to reason about, hard to test). | |
| * It centralizes unrelated behavior, so surface area mushrooms over time. | |
| * It hides the real seams (combat, team transfer, etc.) where contributors should work. | |
| Keep the capability (validated commands from UI), but **locate it inside each domain module**, right next to its policies and actions. Small surfaces, clear ownership. | |
| --- | |
| ## Module shape (what contributors touch) | |
| ``` | |
| luarules/gadgets/ | |
| team_transfer/ -- a “domain” | |
| actions/ -- Command handlers (writes) | |
| resource_transfer.lua | |
| unit_transfer.lua | |
| policies/ -- Idempotent policy producers (reads) | |
| unit_sharing_mode.lua | |
| tax_resource_sharing.lua | |
| callins/ -- Engine hooks this module owns exclusively | |
| SyncedActionFallback.lua | |
| ``` | |
| * **actions/** = Commands (“do X”), single code path for effects. | |
| * **policies/** = Pure/idempotent functions that compute `PolicyResult` structs for UI. | |
| * **callins/** = the engine touchpoints the module *owns*; duplicates should be rejected loudly. | |
| Your example: `team_transfer/policies/tax_resource_sharing.lua` publishes amounts, thresholds, tax rates, etc., which the UI can render without guessing. | |
| --- | |
| ## Contract the UI can depend on | |
| Policies publish structured results (typed tables). Example fields the UI can read (from your policies): | |
| * `canShare`, `sharingMode`, `allowTakeBypass` | |
| * `amountSendable`, `amountReceivable`, `taxRate`, `remainingTaxFreeAllowance`, `resourceShareThreshold`, `cumulativeSent`, `overflowSliderEnabled` | |
| > Swap policies at runtime; UI updates automatically. No logic in `gui_advplayerslist`. | |
| --- | |
| ## Tiny taste of the “command hook” API | |
| When a command executes (e.g., a metal transfer), the module runs well-scoped hooks that can also update policy-relevant counters: | |
| ```lua | |
| builder:RegisterPostMetalTransfer(function(transferResult, springRepo) | |
| local cumKey = getCumulativeParam(SharedEnums.ResourceType.METAL) | |
| local current = tonumber(springRepo:GetTeamRulesParam(transferResult.senderTeamId, cumKey)) or 0 | |
| springRepo:SetTeamRulesParam(transferResult.senderTeamId, cumKey, current + transferResult.sent) | |
| end) | |
| ``` | |
| This keeps *effects* and *policy data* together, testable, and local to the domain. | |
| --- | |
| ## Social/process (brief) | |
| * Let people build modules that are expressed via mod options, but keep them **as PRs against specific modules** (quality gates internally, shared patterns between modules). | |
| * A small marketplace later is fine; the module API encourages reuse and better factoring. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment