Skip to content

Commit fa8a021

Browse files
authored
Update kaggriculture readme/agents docs based on competitor feedback (#1385)
Docs clarifications only, no engine changes. Does the package version need to be incremented for these changes?
1 parent 3fe0e0b commit fa8a021

3 files changed

Lines changed: 42 additions & 24 deletions

File tree

kaggle_environments/envs/kaggriculture/AGENTS.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ Kaggriculture is a two-player farming sim. Each player manages a farm and compet
1313
- **Farmer & farm hands** — one main farmer per player, plus up to N hired hands per day. Hire cost is `farmHandCostMult * fib(n)` where `n` is the number of hires already made today; with the default `farmHandCostMult = 1` that's `1, 1, 2, 3, 5, 8, 13, 21, ...` and resets at the start of each day. Each unit independently acts every turn
1414
- **Crops** — Wheat, Carrot, Tomato (ongoing), Strawberry (ongoing), Melon. Each has its own seed cost, growth time, yield curve, and base sale price (see the Object Types table in [README.md](README.md))
1515
- **Watering bonus** — for one-time crops, watering during the bonus window (starting at `ceil(max_yield_day / 2)`) adds 1 unit per day to harvestable yield. `FERTILIZE` doubles that bonus for 3 days. For ongoing crops, scheduled production yields 1 by default, doubled to 2 if both fertilized and watered that day
16-
- **Animals** — Goose (eggs, requires coop), Cow (milk, requires pasture), Sheep (wool, requires pasture). Must be fed wheat daily; `CARE` banks a yield bonus paid out on the next scheduled production; `COLLECT_FERTILIZER` gathers 1 fertilizer/animal/day
16+
- **Animals** — Goose (eggs, requires coop), Cow (milk, requires pasture), Sheep (wool, requires pasture). Must be fed wheat daily; `CARE` banks +1 per fed-and-cared day, paid out in full on the next scheduled production; `COLLECT_FERTILIZER` gathers 1 fertilizer/animal/day. Unlike crops, animals produce indefinitely as long as they are fed — `max_held` caps unharvested product on the tile, not lifetime output
1717
- **Watering / feeding** — plants must be watered and animals fed daily. Two consecutive missed end-of-day refreshes turn plants into weeds and cause animals to escape (unrecoverable). The planting day counts as the first unwatered day
1818
- **Decay** — once a plant passes its max lifespan (one day after `max_yield_day` for one-time crops, one day after the cumulative production cap for ongoing crops), `yield_units` drops by 1 every other turn until 0, at which point the tile becomes a weed
1919
- **Weeds** — every empty unlocked tile has a `weedSpawnChance` (default 0.005) of spawning a weed at end-of-day; clear with `DIG`
2020
- **Shed** — non-seed inventory cap of 100 items. Items beyond the cap at end-of-day drop are discarded. Seeds live in their own slot (no cap, never picked up by `PICKUP``PLANT` consumes them directly)
21-
- **Market** — fixed prices for seeds, animals, and `BUY_PRODUCT` orders; sale prices for harvested produce vary dynamically with market inventory. Price is `base` at the shared starting inventory `I0`, rises as inventory falls, and falls as inventory grows, using a per-resource shape function (`linear`, `sq`, `sqrt`, or `log`) that can differ on each side of `I0` — so gluts hit premium goods (strawberry, melon, milk, wool) hard, driving them to the $1 floor, while staples absorb oversupply more gently (see the Price Function table in [README.md](README.md)). Fertilizer can only be bought, not sold. Each turn, at most `maxMarketOrdersPerTurn` (default 10) orders are processed per player; extras are silently dropped
21+
- **Market** — fixed prices for seeds, animals, and `BUY_PRODUCT` orders; sale prices for harvested produce vary dynamically with market inventory. Price is `base` at the shared starting inventory `I0`, rises as inventory falls, and falls as inventory grows, using a per-resource shape function (`linear`, `sq`, `sqrt`, or `log`) that can differ on each side of `I0` — so gluts hit premium goods (strawberry, melon, milk, wool) hard, driving them to the $1 floor, while staples absorb oversupply more gently (see the Price Function table in [README.md](README.md)). Only wheat and fertilizer can be bought back via `BUY_PRODUCT`; every product can be sold via `SELL`. Each turn, at most `maxMarketOrdersPerTurn` (default 10) orders are processed per player; extras are silently dropped
2222
- **Town** — town center always demands product (1 of each non-fertilizer product every `townCenterSellInterval` turns, default 12, scaling to 2× after day 10 and 4× after day 20). Additional shops unlock every `townShopUnlockInterval` days (default 3, random selection from the remaining pool); each unlocked shop consumes one of every product it demands every `townShopSellInterval` turns (default 4, single-product shops consume 2×) — see the Town Buildings table in [README.md](README.md)
2323
- **Season length** — 24 turns per day × 30 days = 720 turns by default
2424
- **Win condition** — most coins in the bank at the end of the season; ties are possible
@@ -59,11 +59,11 @@ Your agent is a function that receives an observation and returns an action dict
5959
```
6060

6161
Farmer / hand ops:
62-
- Movement: `"NORTH"`, `"SOUTH"`, `"EAST"`, `"WEST"`, `"PASS"`
63-
- Shed / inventory: `"PICKUP" <item> [n]` (from shed), `"PLACE" <item> [n]` (places an animal on a matching structure when standing on it, or drops items into the shed when adjacent to it), `"DROP"` (when shed-adjacent, dumps entire inventory into shed; overflow past `shedCapacity` discarded)
62+
- Movement: `"NORTH"`, `"SOUTH"`, `"EAST"`, `"WEST"`, `"PASS"`. Locked tiles are passable — units can cross unbought quadrants but cannot act there
63+
- Shed / inventory: `"PICKUP" <item> [n]` (from shed), `"PLACE" <item> [n]` (places an animal on a matching structure when standing on it, or drops items into the shed when adjacent to it), `"DROP"` (when shed-adjacent, dumps entire inventory into shed; overflow past `shedCapacity` discarded). "Shed-adjacent" means standing on one of the four center tiles: `(4,4)`, `(5,4)`, `(4,5)`, `(5,5)`. The shed itself is not in `tiles`.
6464
- Plants: `"PLANT" <crop>`, `"WATER"`, `"HARVEST"`, `"FERTILIZE"`
6565
- Animals: `"BUILD_COOP"`, `"BUILD_PASTURE"`, `"FEED"`, `"COLLECT_FERTILIZER"`, `"CARE"`
66-
- Terrain: `"DIG"` (removes a plant, weed, coop, or pasture from the current tile)
66+
- Terrain: `"DIG"` (removes a plant, weed, or *empty* coop/pasture from the current tile; a structure with an animal on it cannot be dug)
6767

6868
Market ops: `["BUY_SEED", crop, n]`, `["BUY_PRODUCT", item, n]`, `["BUY_ANIMAL", animal, n]`, `["SELL", item, n]`, `["HIRE"]`, `["BUY_LAND"]`. Invalid actions are silent no-ops.
6969

kaggle_environments/envs/kaggriculture/README.md

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,26 @@ Each player starts with an empty farm and a small amount of income (seed money,
88

99
## Object Types
1010

11-
| Type | Yield Type | Seed Cost | Base Market Price | Time to First Yield | Time to Max Yield | Subsequent Yields | Max Yield | Action Cost | Max yield / tile / DAY |
11+
| Type | Yield Type | Seed Cost | Base Market Price | Time to First Yield | Time to Max Yield | Subsequent Yields | Max Yield | Action Cost | Yield / tile / day |
1212
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
13-
| **Wheat** | One-time | 10 | 25 | 2 days | 4 days | none | 6 | 1 | 1.5 |
14-
| **Carrot** | One-time | 20 | 35 | 2 days | 3 days | none | 4 | 1 | 1.333 |
15-
| **Tomato** | Ongoing | 50 | 60 | 8 days | NA | every day | 4 | 1 | 4 |
16-
| **Strawberry** | Ongoing | 100 | 120 | 10 days | NA | every other day | 4 | 1 | 2 |
17-
| **Melon** | One-time | 80 | 250 | 10 days | 12 days | none | 6 | 1 | .5 |
18-
| **Goose/Egg** | Ongoing | 300 | 50 | 4 days | NA | every day | 4 | 1 \+ 1 (build coop) | 2 |
19-
| **Cow/Milk** | Ongoing | 400 | 160 | 8 days | NA | every two days | 6 | 1 \+ 1 (build pasture) | 1 |
20-
| **Sheep/Wool** | Ongoing | 500 | 200 | 6 days | NA | every three days | 6 | 1 \+ 1 (build pasture) | .67 |
13+
| **Wheat** | One-time | 10 | 25 | 2 days | 4 days | none | 6 (4 unfertilized) | 1 | 0.80 |
14+
| **Carrot** | One-time | 20 | 35 | 2 days | 3 days | none | 4 (3 unfertilized) | 1 | 0.75 |
15+
| **Tomato** | Ongoing | 50 | 60 | 8 days | 11 days | every day ×4 | 4 | 1 | 0.33 |
16+
| **Strawberry** | Ongoing | 100 | 120 | 10 days | 16 days | every other day ×4 | 4 | 1 | 0.24 |
17+
| **Melon** | One-time | 80 | 250 | 10 days | 10 days | none | 6 | 1 | 0.55 |
18+
| **Goose/Egg** | Ongoing | 300 | 50 | 4 days | NA | every day, indefinitely | 4 held | 1 \+ 1 (build coop) | 1.00 |
19+
| **Cow/Milk** | Ongoing | 400 | 160 | 8 days | NA | every two days, indefinitely | 6 held | 1 \+ 1 (build pasture) | 0.50 |
20+
| **Sheep/Wool** | Ongoing | 500 | 200 | 6 days | NA | every three days, indefinitely | 6 held | 1 \+ 1 (build pasture) | 0.33 |
2121
| **Fertilizer** | NA | 100 | X | | X | X | | 1 | |
2222

23+
For crops, "Yield / tile / day" is total units harvested divided by the days the tile is occupied, watering daily and harvesting at peak yield. For animals it is the steady-state production rate (`1 / interval`) once the first yield lands; animals keep producing for as long as they are fed, so there is no fixed occupancy to divide by. "Max Yield" for animals is `max_held`, the cap on *unharvested* product sitting on the tile, not a lifetime total.
24+
25+
Crop "Time to Max Yield" is the age at which yield stops increasing under daily watering, which is not always the end of the bonus window:
26+
27+
- **Melon**'s bonus window is ages 6–12, but base 1 plus one unit per watered day reaches the cap of 6 at age 10, so ages 11–12 add nothing. Fertilizing reaches the cap at age 8.
28+
- **Wheat** and **Carrot** only reach their listed Max Yield of 6 and 4 with fertilizer; watering alone peaks at 4 and 3.
29+
- **Tomato** and **Strawberry** are ongoing but *not* indefinite: production is capped at 4 scheduled yields (tomato at ages 8–11, strawberry at ages 10, 12, 14, 16), after which the plant decays into a weed.
30+
2331
All plants must be watered every day. They will turn into weeds if they are not watered for two successive days. All animals must be fed every day using wheat. They will escape and be unrecoverable if they are not fed for two successive days. Wheat is also available to buy at the market and can be purchased at the current market price.
2432

2533
## Actions
@@ -32,7 +40,7 @@ Each Farmer / Farm Hand can be given an action every turn. Farmer/Farm Hand CAN
3240

3341
#### Movement
3442

35-
- NORTH, SOUTH, EAST, WEST — Move one cell in that direction
43+
- NORTH, SOUTH, EAST, WEST — Move one cell in that direction. Moves off the edge of the board are no-ops. Locked tiles are passable: a unit may move onto and across unbought quadrants, but tile actions (`PLANT`, `WATER`, `BUILD_*`, etc.) all no-op on a locked tile and consume nothing.
3644

3745
#### Shed
3846

@@ -59,23 +67,23 @@ Picks up an item from the shed (must be orthogonally adjacent) into the inventor
5967
- **Shed drop**: standing orthogonally adjacent to the shed moves up to `n` (default 1) of `<item>` from inventory into the shed. Capped by `shedCapacity`; excess stays in inventory.
6068
- FEED — Feed an animal using wheat (only needs to be done once per day)
6169
- HARVEST — Collect the eggs/milk/wool produced by the animal.
62-
- COLLECT\_FERTILIZER — Collect 1 fertilizer from the animal. Each surviving animal makes 1 fertilizer available at the end of every day; collecting consumes that day's stock and the next becomes available after the next end-of-day refresh.
70+
- COLLECT\_FERTILIZER — Collect 1 fertilizer from the animal. Every surviving animal makes 1 available at the end of each day, whether or not it was fed or cared for. Uncollected fertilizer does not accumulate, so an animal left alone for five days still yields 1 unit.
6371
- CARE — Care for an animal (once per day, no-op if already cared for). See animal care below.
6472

6573
#### Animal Care
6674

6775
CARE banks a yield bonus that is paid out on the animal's next scheduled production:
6876

69-
* At end of day, if the animal was both fed AND cared for that day, `pending_care_bonus` increments by 2. Days where the animal was unfed do not bank a bonus (basic needs first).
77+
* At end of day, if the animal was both fed AND cared for that day, `pending_care_bonus` increments by 1. Days where the animal was unfed do not bank a bonus (basic needs first).
7078
* On a scheduled production day, if the animal is fed, the entire banked bonus is added to that production's yield (in addition to the base 1) and the bank resets to 0.
71-
* If the animal is unfed on the production day, no yield is produced that day and the bank is also reset.
79+
* If the animal is unfed on the production day, the base 1 unit is still produced, but the banked bonus is not applied and the bank resets to 0.
7280
* `pending_care_bonus` is capped indirectly by the per-animal `max_held` cap on `yield_units`.
7381

7482
#### Terrain
7583

7684
- BUILD\_COOP \- adds a coop to an unoccupied tile
7785
- BUILD\_PASTURE \- add pasture to an unoccupied tile
78-
- DIG — Remove a plant from a square to free up space OR remove a weed from a square (does not yield any produce) OR remove a goose coop / pasture.
86+
- DIG — Remove a plant from a square to free up space OR remove a weed from a square (does not yield any produce) OR remove an **empty** goose coop / pasture. A coop or pasture with an animal on it cannot be dug; the DIG is a no-op.
7987

8088
#### Other
8189

@@ -102,6 +110,10 @@ Each turn you can submit up to `maxMarketOrdersPerTurn` (default 10) market acti
102110

103111
Plants (and animals) must be watered/fed a minimum of every other day. Watering only needs to be done once per day, and subsequent watering actions are a no-op. In the case of plants not watered for two consecutive days, at the end of the day they turn into a WEED. In the case of animals they escape (unrecoverable).
104112

113+
A new seed starts with `consecutive_unwatered = 1` — the planting day itself counts as the first missed day. A seed planted and left unwatered that same day reaches 2 at the end-of-day refresh and becomes a weed that night, before it grows. There is no grace period for fresh plantings.
114+
115+
A newly placed animal starts with `consecutive_unfed = 0`, so it survives its first day unfed.
116+
105117
Note that watering one-time yield plants during their yield window results in a higher yield. This is NOT true for ongoing yield plants/animals. See below.
106118

107119
## Harvest Yields
@@ -134,6 +146,8 @@ Each player has their own farm with a set number of squares. Players are unable
134146
- Farmer and hired farm hands drop their inventory at the end of the day in the shed (if there is room)
135147
- Limited to 100 items, excluding seeds. Once the shed is full, any further items added (via `PLACE` mid-day or end-of-day inventory drop) are discarded — there is no overflow holding area, so stockpiling on farmer/hand inventories does not bypass the cap.
136148

149+
The shed sits at the center of the board and is not a tile — it never appears in the `tiles` array, whose only values are `None`, `"LOCKED"`, and structure dicts. "Orthogonally adjacent to the shed" means standing on one of the four center tiles, `(half-1, half-1)`, `(half, half-1)`, `(half-1, half)`, `(half, half)` for `half = boardSize // 2`. At the default `boardSize = 10` those are `(4,4)`, `(5,4)`, `(4,5)`, and `(5,5)`, one in each quadrant.
150+
137151
### Farmer/Farm Hand
138152

139153
#### Hiring
@@ -142,6 +156,7 @@ Each player has their own farm with a set number of squares. Players are unable
142156
- Cost is `farmHandCostMult * fib(n)` where `n` is the number of hires already made today (fib starts 1, 1, 2, 3, 5, 8, 13, ...).
143157
- With the default `farmHandCostMult = 1`: 1, 1, 2, 3, 5, 8, 13, 21, etc… (resets at the start of each day)
144158
- A hired hand appears orthogonally adjacent to the shed in a free space following NWSE. If there are not open spaces, it looks for the one with the least occupants, breaking ties by NWSE preference
159+
- Spawn placement ignores whether the tile is locked. Since the main farmer starts on `(4,4)`, the least-occupied rule sends the first hire of each day to `(5,4)`, which is locked until the NE quadrant is bought. Locked tiles are passable, so a hand spawned on one can move back to unlocked land.
145160

146161
#### Inventory
147162

@@ -182,7 +197,7 @@ If the sell price has been driven down to `$1` (the price floor), the unit is st
182197

183198
### Buying inventory from the market
184199

185-
Only `WHEAT` and `FERTILIZER` can be bought from the market via `BUY_PRODUCT` (other products are sold at the market but not bought back). Two things drain market inventory: town buildings (town center and shops, which consume products for free) and player `BUY_PRODUCT` orders. Buy orders follow the same one-unit-at-a-time concurrent procedure as sell orders. If a player runs out of money mid-order, the order is stopped.
200+
Only `WHEAT` and `FERTILIZER` can be bought from the market via `BUY_PRODUCT` (other products are sold at the market but not bought back). Selling is unrestricted: every product, including fertilizer collected from animals, can be sold via `SELL`. Two things drain market inventory: town buildings (town center and shops, which consume products for free) and player `BUY_PRODUCT` orders. Buy orders follow the same one-unit-at-a-time concurrent procedure as sell orders. If a player runs out of money mid-order, the order is stopped.
186201

187202
The buy price is quoted at the post-buy inventory and the sell price is quoted at the pre-sell inventory, so an immediate buy followed by a sell of the same item against an otherwise-unchanged market nets exactly zero.
188203

@@ -200,7 +215,9 @@ price(inv) = base + sign · amp · f(|inv − I0|)
200215

201216
Floored at `$1` and rounded to the nearest dollar.
202217

203-
`T` is the production capacity of a single 5×5 field over a 24-day game at optimal watering with no fertilizer (animal totals are pre-discounted by 30% to account for wheat-feed overhead). `target` says "moving `T` units past `I0` shifts the price by `target × base`." Picking different `f` and `target` on each side lets resources with similar production profiles play very differently strategically — wheat panics on scarcity but absorbs gluts, carrot is the opposite; melon barely reacts to scarcity but crashes hard on overproduction; wool mirrors melon at a smaller scale. Premium resources (base > $100: strawberry, melon, milk, wool) use `above_target > 1`, so even modest gluts drive them straight to the $1 floor — bundling and timing sales matters more for these than for staples.
218+
`T` is the production capacity of a single 5×5 field over a 24-day window at optimal watering with no fertilizer (animal totals are pre-discounted by 30% to account for wheat-feed overhead, and allow one day to build the coop or pasture). The 24-day window is a calibration horizon, not the 30-day season length. It is shorter on purpose: the opening days are setup-heavy and yield little.
219+
220+
`target` says "moving `T` units past `I0` shifts the price by `target × base`." Picking different `f` and `target` on each side lets resources with similar production profiles play very differently strategically — wheat panics on scarcity but absorbs gluts, carrot is the opposite; melon barely reacts to scarcity but crashes hard on overproduction; wool mirrors melon at a smaller scale. Premium resources (base > $100: strawberry, melon, milk, wool) use `above_target > 1`, so even modest gluts drive them straight to the $1 floor — bundling and timing sales matters more for these than for staples.
204221

205222
| Resource | Base | I0 | T | Below func | Below target | Above func | Above target | P(I0−T) | P(I0+T) | P(I0+2T) |
206223
| ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
@@ -302,7 +319,7 @@ A `tile` is one of:
302319
"fed_today": bool,
303320
"consecutive_unfed": int, # 2+ → animal escapes
304321
"cared_today": bool,
305-
"fertilizer_available": bool, # set after CARE; cleared by COLLECT_FERTILIZER
322+
"fertilizer_available": bool, # set at end-of-day for every surviving animal; cleared by COLLECT_FERTILIZER
306323
"pending_care_bonus": int, # banked CARE bonus, applied on the next yield tick
307324
}
308325
```

kaggle_environments/envs/kaggriculture/kaggriculture.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
# sign = +1 below I0 (scarcity), -1 above I0 (glut)
3030
# amp = target * base / f(T) (derived; selling T units moves
3131
# price by `target` * base)
32-
# T = production capacity of one 5x5 field over a 24-day game at
32+
# T = production capacity of one 5x5 field over a 24-day window at
3333
# optimal watering, no fertilizer (animal T pre-discounted 30% for
34-
# wheat-feed overhead)
34+
# wheat-feed overhead). Shorter than the 30-day season on purpose:
35+
# the opening days are setup-heavy and yield little.
3536
# f in {linear, sq, sqrt, log, log10}; log uses ln(1+x) so f(0)=0
3637
# Floored at PRICE_FLOOR.
3738
MARKET_I0 = 10000

0 commit comments

Comments
 (0)