All work

Candidate Generation

The overseer for one applicant is the CandidateCase. It owns the profile, the full document set, and any flaws that only make sense across documents, like a CV that contradicts the application form. A single call to GetAllErrors walks the whole tree and returns one flat list, which is exactly what the gameplay loop validates the player's submission against. Element identity is data, not class: two runtime element types are enough because the real differentiation lives on ScriptableObject definitions.

The overseer

CandidateCase holds the Profile (name, date of birth, salary expectation, work-visa status, horoscope, and so on), the documents built from it, and its own document-set-level error list. It drives generation orchestration and aggregates every error across everything it owns. Before a single document renders, it can roll high-level flaws, insufficient or irrelevant experience, that mark a candidate as doomed from the start.

Two-pass generation

Pass one is top-down: the profile generator runs and rolls its data-level errors, documents construct from the profile, elements populate via subscription, containers scope their child data, and leaves roll their own errors. Pass two is bottom-up, depth-first post-order: each container's OnChildrenPopulated fires once its descendants are done, so it can roll errors that depend on populated children, employment gaps, all-same-company, then documents roll theirs, and finally the case rolls cross-document mismatches.

Profile binding and definitions

An ElementDefinition is an asset describing one element type: what profile data it binds to, its cross-cutting tags, whether it's a container, how it lays out children (static, templated-repeat, or none), and default render hints. For the MVP, binding is an enum resolved by a central ProfileDataResolver with a single switch, so a new profile field means one new enum case, with room to promote to per-binding assets later if flexibility demands it.