feat: a make that can fail, and experience that depends on what is worn #104

Merged
sickday merged 1 commit from feat/smelting-roll into main 2026-08-02 18:31:47 +00:00
Owner

Iron smelts half the time and the Goldsmith gauntlets pay 56.2 for a gold bar.
Both were stated in smithing.exs and read by nothing.

The roll is in the loop, not in the contract

Contract.Production must keep having no chance in it. A recipe never fails
is what makes runs/2 and net_slots/2 arithmetic, and Make All resolves
against that arithmetic before a single die is thrown — a roll inside the
contract would make the quantity a player asked for depend on luck they had
not had yet.

So make/4 rolls the recipe's chance through
Contract.Skilling.success?/2 — the same roll every gathering skill takes —
after consuming and before producing. A recipe with no chance is certain,
which is every row in every other skill's file.

Three things about a failed make:

  • it consumes its inputs and produces nothing;
  • it awards nothing;
  • it is not an error. {:error, _} still means cannot make, which
    stops the batch. A smelter who fails one ore goes on to the next.

certain_with overrides the roll rather than improving it — a Ring of
forging does not make iron likelier, it makes it sure — which is why its test
uses the dice that loses every time. It must be worn, and a separate test
asserts that the same ring in the pack does nothing.

dice is an argument with a default, matching the gathering loops: a test
states the roll rather than surviving it.

experience_with is a value, not a multiplier

22.5 to 56.2 is a ratio of 2.4977…, so deriving the second from the first
gives 56.25 and starts a rounding argument nobody needs to have. The wiki
prints both numbers and the table carries both.

Woodcutting's Lumberjack outfit genuinely is a percentage and stays one.
These are not the same mechanic wearing a different hat, and the test says so
in its message.

A latent crash shipped in #103

animate/2 has no catch-all clause, on purpose — a recipe that omits
animation should raise rather than silently show nothing. The four godsword
hilt rows carried no animation key, so attaching a hilt would have raised
FunctionClauseError the first time anybody did it
. No test made a hilt, so
nothing caught it.

Fixed with an explicit animation: :none, plus a test that walks every recipe
in every table — pairs, screen rows and station products — and asserts the key
exists. That one now guards Fletching and Crafting too.

Three stale tests repaired

clicking Make-1 on the ring row makes a ring, Make-5 is op 2 and the mould survives every make of a batch all asserted that a make completes
inline on the click. The skilling clock ended that deliberately, and the
neighbouring test — nothing is made on the tick the batch starts — asserts
the new behaviour and passes in the same file. They were left red by that
change rather than by anything here, and now advance the clock a period
before asserting.

A comment corrected

The lovakite note claimed "Lovakengj smelts at its own furnace by its own
route". That was a conclusion nobody had checked: no loc anywhere in the
cache carries a Smelt or Forge action outside the sixteen furnaces
already declared.
So either lovakite smelts at an ordinary furnace by a
route 311 does not draw, or it is not smeltable in this cache at all. The
comment now says that, and warns the next reader not to assume a second
furnace exists.

Not modelled

The Ring of forging's charges. It degrades after a number of iron bars in
the live game and nothing counts them, because no durability system exists.
The ring is currently infinite and the data file says so.

Checks

mix revenant.ci --gates passes every leg, Dialyzer included.
production_test.exs is 67/67, including 11 new tests.

The failure picture, measured rather than argued

Ran production_test.exs and players_test.exs together on the bare base and
again with this diff. Bare base: 4 failures. With this branch: 1.

Test Status
3 x ProductionTest inline-make fixed here
World.TickSnapshotTest pre-existing — the row gained :setup? from the account-setup work and the expected list was not updated
Database.PlayersTest round trip pre-existing, and accumulating
World.Content.LightTest retry pre-existing, Firemaking

The persistence one is worth its own issue rather than a shrug: prayer tenths
read 45, then 90, then 135 across successive runs, so the file-backed test
database is retaining rows between runs. It passes in isolation and fails in
company, which means it will keep reading as a random flake until the reset is
fixed. That is the same fixture-uniqueness bug the admin panel work hit once.

LightTest is deliberately untouched. Unlike the production three it is not
obviously a stale assertion — the timer is gone where the test expects it
retained, which may be a real bug in the light loop, and guessing at it inside
a Smithing branch is how a wrong fix gets buried in an unrelated diff.

Iron smelts half the time and the Goldsmith gauntlets pay 56.2 for a gold bar. Both were stated in `smithing.exs` and read by nothing. ## The roll is in the loop, not in the contract `Contract.Production` must keep having no chance in it. *A recipe never fails* is what makes `runs/2` and `net_slots/2` arithmetic, and `Make All` resolves against that arithmetic **before a single die is thrown** — a roll inside the contract would make the quantity a player asked for depend on luck they had not had yet. So `make/4` rolls the recipe's `chance` through `Contract.Skilling.success?/2` — the same roll every gathering skill takes — after consuming and before producing. A recipe with no `chance` is certain, which is every row in every other skill's file. Three things about a failed make: * it **consumes its inputs and produces nothing**; * it awards nothing; * it is **not an error**. `{:error, _}` still means *cannot make*, which stops the batch. A smelter who fails one ore goes on to the next. `certain_with` **overrides** the roll rather than improving it — a Ring of forging does not make iron likelier, it makes it sure — which is why its test uses the dice that loses every time. It must be **worn**, and a separate test asserts that the same ring in the pack does nothing. `dice` is an argument with a default, matching the gathering loops: a test states the roll rather than surviving it. ## `experience_with` is a value, not a multiplier 22.5 to 56.2 is a ratio of 2.4977…, so deriving the second from the first gives 56.25 and starts a rounding argument nobody needs to have. The wiki prints both numbers and the table carries both. Woodcutting's Lumberjack outfit genuinely *is* a percentage and stays one. These are not the same mechanic wearing a different hat, and the test says so in its message. ## A latent crash shipped in #103 `animate/2` has **no catch-all clause**, on purpose — a recipe that omits `animation` should raise rather than silently show nothing. The four godsword hilt rows carried no `animation` key, so **attaching a hilt would have raised `FunctionClauseError` the first time anybody did it**. No test made a hilt, so nothing caught it. Fixed with an explicit `animation: :none`, plus a test that walks every recipe in every table — pairs, screen rows and station products — and asserts the key exists. That one now guards Fletching and Crafting too. ## Three stale tests repaired `clicking Make-1 on the ring row makes a ring`, `Make-5 is op 2` and `the mould survives every make of a batch` all asserted that a make completes **inline on the click**. The skilling clock ended that deliberately, and the neighbouring test — `nothing is made on the tick the batch starts` — asserts the new behaviour and passes in the same file. They were left red by that change rather than by anything here, and now advance the clock a period before asserting. ## A comment corrected The lovakite note claimed "Lovakengj smelts at its own furnace by its own route". That was a conclusion nobody had checked: **no loc anywhere in the cache carries a `Smelt` or `Forge` action outside the sixteen furnaces already declared.** So either lovakite smelts at an ordinary furnace by a route `311` does not draw, or it is not smeltable in this cache at all. The comment now says that, and warns the next reader not to assume a second furnace exists. ## Not modelled The Ring of forging's **charges**. It degrades after a number of iron bars in the live game and nothing counts them, because no durability system exists. The ring is currently infinite and the data file says so. ## Checks `mix revenant.ci --gates` passes every leg, Dialyzer included. `production_test.exs` is **67/67**, including 11 new tests. ### The failure picture, measured rather than argued Ran `production_test.exs` and `players_test.exs` together on the bare base and again with this diff. **Bare base: 4 failures. With this branch: 1.** | Test | Status | |---|---| | 3 x `ProductionTest` inline-make | **fixed here** | | `World.TickSnapshotTest` | pre-existing — the row gained `:setup?` from the account-setup work and the expected list was not updated | | `Database.PlayersTest` round trip | pre-existing, and **accumulating** | | `World.Content.LightTest` retry | pre-existing, Firemaking | The persistence one is worth its own issue rather than a shrug: prayer tenths read **45, then 90, then 135** across successive runs, so the file-backed test database is retaining rows between runs. It passes in isolation and fails in company, which means it will keep reading as a random flake until the reset is fixed. That is the same fixture-uniqueness bug the admin panel work hit once. `LightTest` is deliberately untouched. Unlike the production three it is not obviously a stale assertion — the timer is gone where the test expects it retained, which may be a real bug in the light loop, and guessing at it inside a Smithing branch is how a wrong fix gets buried in an unrelated diff.
feat: a make that can fail, and experience that depends on what is worn
All checks were successful
ci / gates (pull_request) Successful in 43s
ci / gates (push) Successful in 45s
b3850cccd4
Iron smelts half the time and the Goldsmith gauntlets pay 56.2 for a gold
bar. Both were stated in smithing.exs and read by nothing.

The roll is in the loop and not in the contract, deliberately.
Contract.Production must keep having no chance in it -- a recipe never fails
is what makes runs/2 and net_slots/2 arithmetic, and Make All resolves
against that arithmetic before a single die is thrown. So make/4 rolls the
recipe's chance through Contract.Skilling.success?/2, after consuming and
before producing, and a recipe with no chance is certain.

Three things about a failed make. It consumes its inputs and produces
nothing. It awards nothing. And it is not an error -- {:error, _} still means
cannot make, which stops the batch, whereas a smelter who fails one ore goes
on to the next.

certain_with overrides the roll rather than improving it: a Ring of forging
does not make iron likelier, it makes it sure, which is why its test uses the
dice that loses every time. It has to be worn, and a test says so. Its
charges are not modelled, because no durability system exists.

experience_with states an alternative value rather than a multiplier. 22.5 to
56.2 is a ratio of 2.4977..., so deriving the second from the first gives
56.25 and starts a rounding argument. Woodcutting's Lumberjack outfit really
is a percentage and stays one.

Also fixes a latent crash shipped with the smithing table: animate/2 has no
catch-all clause and the four hilt rows carried no animation key, so
attaching a hilt would have raised the first time anybody did it. No test
made a hilt. There is now one that walks every recipe in every table and
asserts the key exists, which guards all three skills.

Three tests in production_test asserted that a make completes inline on the
click. The skilling clock ended that on purpose and the neighbouring test
asserts the new behaviour, so these were left red by that change rather than
by anything here. They now advance the clock a period before asserting.

Corrects a comment that stated a conclusion nobody had checked: lovakite does
not smelt at "its own furnace", because no loc anywhere in the cache carries
a Smelt or Forge action outside the sixteen already declared.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Revenant/Server!104
No description provided.