BeamNG.Earth
BeamNG.drive용 1:1 비율 실시간 지구, 실제 고도·위성 이미지·부동 원점·보조 서버에서 LOD 스트리밍
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Deterministic Frontend Architecture for Complex Interactive Systems
This is a web application built to explore how complex, highly interdependent rulesets can be modeled in a way that keeps the frontend predictable, testable, and resilient under heavy interaction.
While themed as a tabletop RPG assistance tool, the project’s primary focus is frontend architecture: extracting a deterministic domain layer that dramatically reduces UI complexity and state divergence in React applications.README 전문 보기 ↗Live Demo: https://fallow-app.vercel.app/
This is a web application built to explore how complex, highly interdependent rulesets can be modeled in a way that keeps the frontend predictable, testable, and resilient under heavy interaction.
While themed as a tabletop RPG assistance tool, the project’s primary focus is frontend architecture: extracting a deterministic domain layer that dramatically reduces UI complexity and state divergence in React applications.
This project is intentionally over-scoped in domain complexity to stress-test frontend architecture decisions.
It explores:
The goal is not to ship a complete game.
The goal is to demonstrate how a complex system can remain coherent, explainable, and maintainable as interaction density increases — a common failure point in large frontend codebases. This project prioritizes clarity under complexity, not framework cleverness.
This project is best evaluated by examining how rule changes propagate through the system. Most architectural decisions are visible in the domain layer and its relationship to the UI, rather than in feature completeness or visual polish.
At a high level, the system is structured as:
Domain (types, rules, selectors, updaters)
↓
React hooks & thin state layers
↓
UI components
React is treated as an integration layer; authoritative game logic is not implemented inside components or hooks.
All authoritative logic lives in a deterministic domain layer that can be:
The most challenging and intentional part of this project was designing a domain layer that is UI-agnostic, yet frontend-driven.
This inversion dramatically reduces:
app/
├── components/ # Thin UI & interaction panels
├── */.json # JSON templates (domain inputs: characters, equipment, etc.)
├── domain/ # Deterministic rules, factories, selectors
├── hooks/ # UI-facing access to domain/state
├── stores/ # Localized Zustand slices (non-authoritative)
├── actions.ts # Server actions (persistence boundaries)
└── page.tsx # Application entry point
⚠️ Note on Zustand Zustand is used sparingly for localized coordination. It is not used as a global derived-state store. All authoritative game logic remains in the domain layer.
The domain layer is authoritative, so most changes start there and flow outward. Data moves in one direction:
JSON templates (app/**/*.json) → Zod schemas (app/domain/types.ts) → factories (app/domain/factories.ts) → rules + lenses/projections + commands (app/domain/**) → stores/hooks → components
When implementing a feature or fix:
Heuristics:
app/domain/character/rules/** (or scene/rules/**); a stat the sheet edits also gets a lens registered in lenses/index.ts; a shape the UI renders is a view getter in character/lenses/** or a projection in scene/projections/** / item/projections/**.app/domain/**/commands/** returning a new aggregate.app/actions.ts) or redis.ts; treat persistence as I/O, not rule evaluation.Non-negotiable constraints:
All character data flows through the Character / CampaignCharacter aggregate.
Domain functions stay synchronous, pure (no I/O, time, or randomness unless explicitly modeled), and stable under recomputation.
Don't add a setter that bypasses the derived-stat invariant floor(0.5 * STR * DM + base) — edit the stored base term, not the derived output.
No component performs arithmetic on a domain number. Components render values and call handlers; if a component needs a + b, the domain owes it a getter. This is the rule that keeps the thesis honest — every rule leak this project has had (an armor tier table in JSX, an STA-regen formula in a header, a STR-mod damage formula duplicated between a panel and a command) began as one small expression inside a component that nobody thought was a "rule".
When the domain doesn't already expose the shape you need, that's the signal to add a view getter, not to compute it in the component. A view getter is a pure (c: Character) => RenderableShape — the same pattern as getStrikeTerms, extended past scalars to rows and tables (getDamageTiers, getWeaponAttackRows). The tell that you're about to leak a rule is a component reading a raw characteristic (STR, STA, TGH) — it's reading it in order to do arithmetic with it.
ESLint rules enforce the boundary mechanically (eslint.config.mjs), so this is a build failure rather than a code-review habit:
app/domain/** may not import from app/stores/**, React, or Zustand — the domain defines its own types and the store imports them, never the reverse.rules/, lenses/ and projections/ may not import commands/ or the bot — the read side never runs a button to find out an answer.app/domain/{character,item}/** may not form an import cycle — size and scale, then items, then the character's state, then AP prices; a file that imports upward mixes two of those and is split.app/components/** may not import from **/domain/*/rules/**, **/domain/*/lenses/**, **/domain/*/projections/** or **/domain/*/commands/** — go through a hook in app/hooks/. Type-only imports are allowed via import type. Inert modules (domain/types, domain/tables, domain/factories, , ) stay importable. is exempt: it's a stress-test harness whose whole purpose is to drive the lens registry and the store directly.Game mechanics exist to anchor abstractions, not to showcase feature breadth.
They were chosen specifically because they create non-linear, cascading effects, which are useful for testing deterministic modeling.
Examples include:
These mechanics ensure that small changes can have wide-reaching effects — exactly the kind of scenario that breaks naïve frontend state management.
git clone https://github.com/gbelow/udgs-runtime.git
cd udgs-runtime
pnpm install
Create .env:
UPSTASH_REDIS_URL=...
UPSTASH_REDIS_TOKEN=...
Run locally:
pnpm dev
Open:
http://localhost:3000
This project prioritizes long-term coherence and change tolerance over short-term feature velocity. The central architectural decision was to model the game as a deterministic domain that fully owns rules, invariants, and data interpretation, with the UI acting purely as a consumer of domain outputs. This allows rule changes to be implemented by modifying the domain layer alone, without requiring coordinated updates across components, stores, or persistence logic.
State in the application is treated as derived by definition: the domain describes how the system behaves, not merely the shape of stored data. As a result, changing data or rules implies updating the domain logic rather than patching downstream consumers. This consolidation was intentional—it trades flexibility at the edges for clarity at the core, and prevents the UI from silently diverging from the rules it is meant to represent.
Global state libraries were deliberately avoided for derived logic. Redux was rejected due to its boilerplate cost and focus on action-based mutation rather than memoized computation. Zustand is used selectively as a coordination layer, primarily to manage memoization boundaries and prevent unnecessary re-renders under heavy interaction. It does not own rules or derived values; those remain in the domain.
For persistence, Redis was chosen over a relational database because the system operates on self-contained aggregates rather than relational queries. Characters and related entities are loaded and saved as whole objects, without joins. This simplifies iteration but introduces the responsibility of handling data evolution explicitly. The system assumes that data shape changes are intentional and versioned, with the domain acting as the authoritative interpreter of stored data rather than relying on implicit schema guarantees.
JSON files are used for domain inputs (characters, equipment, rules tables) both for transparency and for ease of bulk editing. This choice made experimentation and reshaping faster during development and reinforced the separation between data definition and rule evaluation. While not optimal for all production scenarios, it was valuable for exploring how rule changes propagate through the system.
This architecture required more upfront design and refactoring than a feature-first approach. However, the resulting system is significantly more scalable: boundaries are explicit, responsibilities are clear, and future expansion is expected to be faster and safer precisely because the difficult decisions have already been centralized.
These tradeoffs were intentional.
Private project Not licensed for commercial or public distribution.
Made by Guilherme Below Full-Stack Engineer
domain/utilsdomain/play/diceBreakMe.tsx