Ming fiscal case
The scenario
Section titled “The scenario”A Ming revenue office assesses 100 units of tax, authorizes a collection of 70, and waits for proof that the 70 arrived. When a resource adapter reports the collection, the fiscal extension records a receipt, and the revenue minister receives an estimate of what is assessed, collected, and still owed. Three starts run this cycle in different settings: Hongwu 1391 (registered land, labor service, and grain obligations in the Lower Yangzi), Wanli 1581 (Single Whip reform at different stages across four regions), and Hongguang 1644 (five separate fiscal authorities of the Southern Ming).
The question the example answers: how does a game model a tax system whose rules change by period and region, while the bookkeeping stays separate from the grain and silver that actually move?
Rules, regions, periods, and adoption stages come from a source-cited content pack. The quantities, people, and institutions are illustrative values from a small synthetic reference world.
What this example shows
Section titled “What this example shows”- The fiscal simulation extension
canwu-fiscal(FiscalPlugin): fiscal rules, their regional adoption, assessments, execution authorizations, and receipts. Resource balances and transfers stay with the resource or logistics domain. - A reference content pack,
canwu-ming-fiscal: periods, regions, rules, reform transitions, and cited sources indata/pack.json, plus the three starts as fixture files. - A reference integration,
canwu-ming-fiscal-reference: builds each start oncanwu-reference-world, registers the plugins, runs the sample cycle, and writes a trace. - Fiscal actions sent as domain commands
(
Command::Plugin) and checked against aFiscalAuthorityBinding. - A fiscal execution receipt
(
FiscalExecutionReceiptPacket) settled from an exact domain record version written by an adapter. - The minister’s report, published as holder-relative knowledge with each total as a range.
Run it
Section titled “Run it”cargo run -p canwu-ming-fiscal-reference --example ming_fiscal_starter -- hongwu-1391cargo run -p canwu-ming-fiscal-reference --example ming_fiscal_starter -- wanli-1581cargo run -p canwu-ming-fiscal-reference --example ming_fiscal_starter -- hongguang-1644The fixture ID is the first argument; without it the starter runs
hongwu-1391. Each run prints one line (trace paths trimmed):
fixture=hongwu-1391 checkpoint=02fa72339b855af10464430523413b67d9d748be200eb0375d57b919d69aa72c frames=6 trace_manifest=... trace_steps=...fixture=wanli-1581 checkpoint=d1d6ebabd9b8b38f8fe1a73d4708dbee999b596990942299ae7b4ea2229d7d2d frames=6 trace_manifest=... trace_steps=...fixture=hongguang-1644 checkpoint=4c93454351280141b4709717b8cad275ceb853b89459115ef9d73392a4cff7b1 frames=6 trace_manifest=... trace_steps=...The seed is fixed (DEFAULT_SEED), so each fixture prints the same checkpoint
hash on every run. frames=6 counts settled boundaries: two for each fiscal
action, one for the adapter’s evidence record, and one for the receipt. The
trace goes to artifacts/traces/ming-fiscal-reference/<fixture>/ in the
workspace: manifest.json, plus one JSON line per boundary in steps.jsonl.
In the last Hongwu line, fiscal.state.aggregates and fiscal.projections
hold the result (trimmed):
"aggregate.0000":{...,"assessed":100,"remission_granted":0,"collected":70,...,"outstanding":30}"fact.0000":{...,"assessed":{"minimum":100,"maximum":109},"collected":{"minimum":70,"maximum":71},"outstanding":{"minimum":30,"maximum":31}}Each start assesses a different adoption:
| Fixture | Historical mode | Assessed rule and scope | Unit |
|---|---|---|---|
hongwu-1391 |
recorded_baseline |
canal_grain_tribute on scope.lower-yangzi.grain |
shi_grain_equivalent |
wanli-1581 |
recorded_baseline |
single_whip_lower_yangzi on scope.lower-yangzi.land |
liang_silver |
hongguang-1644 |
research_replay |
southern_ming_salt_finance on scope.southeast.salt |
liang_silver |
Options go after the fixture ID:
| Option | Effect |
|---|---|
--days <N> |
Continues for N simulation days after the sample cycle, settling one calendar boundary per step. |
--cadence <marker> |
Marks each step daily, monthly, or annual. Monthly and annual steps are 30 and 365 simulation days, because SimTime counts minutes and has no calendar. |
--step-days <N> |
Sets another step length. A shorter final step settles without a cadence marker. |
--trace-dir <path> / CANWU_TRACE_DIR |
Changes the trace root. |
--open-viewer / --viewer-port <N> |
Starts a trace viewer on 127.0.0.1 before the sample cycle and opens the default browser. The viewer loads steps.jsonl as it grows, and the process keeps serving until you press Ctrl+C. The port defaults to 0, which takes a free one. |
See Continuous-time game loop for the
stepping model. The viewer needs the workspace’s tools/trace-viewer/; outside
the workspace, set CANWU_WORKSPACE_ROOT.
Walkthrough
Section titled “Walkthrough”The starter,
ming_fiscal_starter.rs,
calls new_ming_fiscal_reference and run_ming_fiscal_sample_cycle_with_trace
from the integration’s
lib.rs
and writes a trace frame for every boundary receipt.
1. Compile the catalog for one fixture
Section titled “1. Compile the catalog for one fixture”ming_fiscal_reference_scenario loads the fixture JSON, such as
wanli-1581.json,
which names a year, a historical mode, regions, and each rule’s adoption
stage. It compiles the pack for those regions:
let catalog = compile_ming_fiscal(FiscalContentSelection { historical_year: fixture.historical_year, region_ids: fixture.region_ids.clone(), ..FiscalContentSelection::default()})?;The default selection leaves the mechanism set empty, which selects all eleven
mechanisms. The catalog keeps every
period and every rule for those regions; the year chooses the active period,
such as founding_reconstruction for 1391.
2. Build the starting fiscal state
Section titled “2. Build the starting fiscal state”The same function creates a FiscalState. reference_institutions gives
every start a court institution (the reference world’s government) under
authority.revenue-minister; hongguang-1644 adds a field commander, a
regional treasury, a salt administration, and a merchant-credit office. Each
gets a binding with one standing actor:
FiscalAuthorityBinding { id: institution.authority_id.to_owned(), institution: institution.entity.clone(), authorized_actor: Some(institution.actor), acting_actor: None, authority_basis: None,},Each fixture adoption becomes a FiscalScopeBinding (one jurisdiction and
mechanism assigned to an institution at SimulationGranularity::Aggregate)
and an adoption record with the fixture’s stage. The observer binding
observer.revenue-minister lets the minister see those totals with
confidence_per_mille: 850.
3. Register the plugins
Section titled “3. Register the plugins”let canwu = Canwu::new_with_plugins(seed, reference.scenario, &[&world, &adapter, &fiscal])?;validate_ming_fiscal_reference(&canwu)?;new_ming_fiscal_reference registers the reference world, the execution
adapter, and ming_fiscal_plugin(), a FiscalPlugin that accepts one
evidence kind: the adapter’s fiscal_execution_evidence record.
validate_ming_fiscal_reference is the integration’s semantic check; it
recomputes aggregates and reform candidates and re-matches every receipt
against its evidence.
4. Open an assessment and authorize a collection
Section titled “4. Open an assessment and authorize a collection”sample_cycle_plan takes the first adoption, by ID, whose stage is
operational (implemented, audited, or entrenched) and whose rule’s legal
window contains the year. In hongguang-1644 the Taicang treasury rule ends
in 1643, so the cycle uses the salt administration. The first action:
action: FiscalAction::OpenAssessment { assessment_id: assessment_id.clone(), rule_id, scope_binding_id: scope_id, accounting_cycle_id: format!("{period_id}.{id_prefix}"), quantity: 100, unit: sample_unit(payment_form).to_owned(), payment_form, commutation_quote: None,},submit_reference_action_with_trace wraps the FiscalActionRequest with
fiscal_action_command and issues it as the binding’s actor. At the first
boundary, admission checks the actor against the binding, the binding’s
institution against the scope, and expected_procedure_revision against the
state. The next boundary settles the action into action_outcomes. The
accounting cycle ID belongs to the host and is separate from the historical
period. The second action, AuthorizeExecution, permits collecting 70 of
resource ResourceId::new(1) from the actor to the institution; resource
balances stay as they are.
5. Record the collection and settle the receipt
Section titled “5. Record the collection and settle the receipt”settle_sample_execution_with_trace plays the resource side. It sends a
FiscalExecutionEvidence value (70 units, Fulfilled, with an
external_operation_id) through enqueue_reference_execution_result. At
boundary 5 the adapter checks it against the authorized request and writes a
create-only domain record. The function then submits that record’s exact
version:
enqueue_execution_receipt( canwu, now, &canwu_fiscal::FiscalExecutionReceiptPacket { receipt_id: format!("{id_prefix}.receipt"), request_id, external_evidence: [evidence_version].into_iter().collect(), },)?;At boundary 6 FiscalPlugin decodes each cited record, checks its kind and
that its request, execution kind, payment form, resource, source, target, and
unit match the authorization, then derives the receipt’s quantity (70) and
disposition (fulfilled) from it.
6. Read the aggregate and the report
Section titled “6. Read the aggregate and the report”The aggregate now shows 100 assessed, 70 collected, and 30 outstanding. At
each boundary that writes a new fiscal state version, FiscalPlugin publishes
one report per observer binding as a knowledge record held by that observer.
estimate in
derive.rs
rounds each total into a bucket sized by the observer’s confidence:
let precision_divisor = match confidence_per_mille { 900..=1_000 => 100, 750..=899 => 10, 500..=749 => 2, _ => 1,};let bucket_width = (magnitude / precision_divisor).max(2);let minimum = value / bucket_width * bucket_width;magnitude is the largest power of ten at or below the value. At 850 per
mille, 100 gets width 10 (100-109) and 70 gets the minimum width 2 (70-71).
The trace reads the report back through canwu.viewer_for_actor with a
KnowledgeQuery for fiscal_report_knowledge_schema_id()
(collect_projections in trace.rs).
What to notice
Section titled “What to notice”- The collected amount enters fiscal state only from the adapter’s evidence record, two boundaries after the authorization.
- The receipt’s quantity and disposition come from the cited evidence. Within
one fiscal state, each exact evidence version and each pair of evidence kind
and
external_operation_idsettles at most one receipt. - One code path runs all three starts; the fixture JSON picks the rule, scope, and institution.
- The minister reads 100-109 while the authoritative state holds 100.
- In
wanli-1581the trace listsadopt_single_whip_northandadopt_single_whip_southwestas transition candidates. They stay candidates until anApplyTransitionaction settles.
Try changing
Section titled “Try changing”- Report precision. In
ming_fiscal_reference_scenario, set the observer’sconfidence_per_milleto 600. The divisor becomes 2, so the assessed range becomes 100-149 and the collected range 70-74. - Partial collection. In
settle_sample_execution_with_trace, set the evidence toquantity: 40anddisposition: FiscalReceiptDisposition::Partial. The aggregate then shows 40 collected and 60 outstanding. - Apply a reform. In
wanli-1581, submitApplyTransitionforadopt_single_whip_northwithsingle_whip_northbound toscope.north.land, as the testsingle_whip_transition_atomically_suspends_the_superseded_ruledoes. The north’s silver-commutation adoption becomessuspended, asingle_whip_northadoption becomesimplemented, and the north candidate disappears.
Beyond the example
Section titled “Beyond the example”A game adds one more layer: a host adapter that performs the real operation in its resource, market, production, or logistics domain and then submits the evidence, as step 5 does.
Fiscal institutions covers the rest of the model: coverage cells and the Ming pack’s 704-cell breakdown, historical context and reform candidates, acting actors, and state limits.
Periods and sources
Section titled “Periods and sources”The pack covers eight periods from 1368 to 1683 and cites ten sources, Ray
Huang (1974) most often; each source lists inferences it does not support, such
as reading registered households as actual population. The
case note lists the periods, and
data/pack.json lists every rule and source.
Source
Section titled “Source”Open the runnable example
Read the integration tests
Browse the content pack
Read the case note