Play Online
Derby Owners Club · Tested against the ROM

DOC Myths

Common Derby Owners Club claims, tested against the actual arcade ROM. Each one gets a claim, the evidence, and a verdict.

Evidence, not opinion. Every offset is checkable in the real ROM.

The community trades a lot of lore about how Derby Owners Club works. Some of it is true, some of it is half remembered, and some of it is just wrong. This page settles the arguments by going to the source: the game's own program ROM, disassembled and read line by line.

Each myth below has its own shareable link. Click the 🔗 next to any title, then copy the address bar to drop that exact myth into a Discord or Facebook thread.

The myths

More to come. Got one you want tested? See the bottom of the page.

Myth #1

Turf can never be "Good to Soft"

The Claim

"Good to Soft is a dirt-only rating. In Derby Owners Club a turf race can never show Good to Soft. If you see Good to Soft, the surface has to be dirt."

Where it shows up: community Discord and Facebook debates about how the game decides the going.

The Evidence

Derby Owners Club stores the track condition as a single number from 0 to 3, on one going scale, and it is the same scale for every race:

ValueTrack condition
0Good
1Good to Soft
2Soft
3Heavy

That number is generated from the weather and race data, then printed by the on-screen TRACK CONDITION line. Whether the race runs on turf or dirt is stored separately, as part of the race identity; the race name itself carries TURF or DIRT. The code that sets the track condition never looks at the surface.

Four routines in the program ROM write the track condition. Every one of them sets it from weather or race data, and not one of them reads the surface:

Routine (ROM address)How it sets the conditionReads surface?
0x03E778 / 0x03EA32High nibble of a packed race-data byteNo
0x03F956Byte copied straight from the race recordNo
0x054012Lookup in the weather-to-going tableNo

The clincher: the game's own weather-to-going table emits the Good to Soft value directly. Its outputs across all 16 entries are exactly the set {Good, Good to Soft, Soft, Heavy}, and the Good to Soft value appears at two entries:

Table entryTrack condition produced
2Soft
4Good to Soft
7Soft
10Good to Soft
14Heavy
all othersGood

The input to that table is the weather and the race seed. There is no surface input anywhere in the chain. When the weather lands on those entries, the going becomes Good to Soft for the race, turf or dirt alike.

Show the full ROM proof (offsets and disassembly)

All addresses below are reproducible in the standard drbyocwc ROM set: World Edition Rev C program ROM epr-22336c.ic22, CRC32 50053F82. It is a Sega Naomi SH-4 title; the ROM image loads at 0x0C020000, so file offsets and RAM addresses line up directly.

The condition value and its string table. The condition is one global at 0x0C21A2B4 (race-info struct base 0x0C21A294, field +0x20). It indexes a four-pointer string table at file 0x150BD0:

value 0 -> "GOOD"          (file 0x10EB3C)
value 1 -> "GOOD TO SOFT"  (file 0x10EB44)
value 2 -> "SOFT"          (file 0x10EB54)
value 3 -> "HEAVY"         (file 0x10EB5C)

The renderer at RAM 0x0C076B3A reads the value raw and indexes that table. No mask, no surface read:

0x0C076B3A  mov.l  0xc076be4,r3   ; r3 = &condition (0x0C21A2B4)
0x0C076B3C  mov.l  0xc076be8,r0   ; r0 = value table (0x0C170BD0)
0x0C076B3E  mov.l  @r3,r2         ; r2 = condition (0..3), read raw
0x0C076B40  mov.l  0xc076bec,r1   ; r1 = "TRACK CONDITION %s"
0x0C076B42  shll2  r2             ; r2 = condition * 4
0x0C076B46  mov.l  @(r0,r2),r2    ; r2 = table[condition]  (no surface input)
0x0C076B4A  jsr    @r3            ; sprintf

The four writers that set the condition, with the value each one stores:

0x03E778 / 0x03EA32 : condition = (raceData[+0x28] >> 4) & 0x0F
0x03F956            : condition = byte at raceRecord[+0x72]
0x054012            : condition = weatherGoingTable[(seed >> 3) & 0x0F]

None of them loads the surface field or branches on turf versus dirt.

The weather-to-going table at file 0x10FCA4. The going column across all 16 entries is the set {0,1,2,3}; value 1 (Good to Soft) sits at entries 4 and 10. The seed comes from the weather and RNG routine at 0x0C0B1E60:

entry  0 -> Good           entry  8 -> Good
entry  1 -> Good           entry  9 -> Good
entry  2 -> Soft           entry 10 -> Good to Soft
entry  3 -> Good           entry 11 -> Good
entry  4 -> Good to Soft   entry 12 -> Good
entry  5 -> Good           entry 13 -> Good
entry  6 -> Good           entry 14 -> Heavy
entry  7 -> Soft           entry 15 -> Good
distinct outputs = { Good, Good to Soft, Soft, Heavy }

The surface lives in the race identity: the race-name table at file 0x0C6940 carries TURF or DIRT per race. None of the four condition writers read it. Surface and going are independent fields.

Verdict: False

Turf can be Good to Soft. There is no turf exclusion anywhere in the game.

Good to Soft is generated independently of the surface. The game's own going table produces it, and the routine that prints the condition never checks turf or dirt. Turf and dirt draw from the exact same mechanism, so a turf race can absolutely be Good to Soft.

Verify it yourself: open epr-22336c.ic22 (CRC32 50053F82) from the standard drbyocwc ROM set and check the offsets in the proof above.

Footnote: Rev D is different

Everything above is the World Edition Rev C build (the common drbyocwc set). The later Rev D build (World Edition EX, derbyocw) quietly rebuilt the going system, and on Rev D the answer flips: the going IS tied to the surface, and "Good to Soft" was removed from the game entirely.

Rev D uses two separate going vocabularies, one for dirt and one for turf:

Dirt goingTurf going
FastFirm
GoodGood
HeavySoft
MuddyYielding

So on Rev D a turf race can show Firm, Good, Soft, or Yielding, and a dirt race can show Fast, Good, Heavy, or Muddy. There is no "Good to Soft" rating on Rev D at all. The bottom line is that this is version dependent: on Rev C the going is one shared scale and a turf race can be Good to Soft, while on Rev D the going words follow the surface. That Rev D behavior is most likely where the "Good to Soft is a dirt-only rating" belief comes from.

Show the Rev D ROM proof

Rev D program ROM epr-22336d.ic22, CRC32 E6C0CB0C (the derbyocw set). The on-screen condition words are drawn from one string table that is split into a dirt half and a turf half:

condition string table @0x0C173208 (8 going entries):
  index 0  FAST      index 4  FIRM
  index 1  GOOD      index 5  GOOD
  index 2  HEAVY     index 6  SOFT
  index 3  MUDDY     index 7  YIELDING
indices 0-3 = the dirt going set, 4-7 = the turf going set
renderer at 0x0C075676 indexes this table by the going value

The surface-only words make the split unambiguous: Fast and Muddy (dirt terms) sit in the lower half at 0 and 3, while Firm and Yielding (turf terms) sit in the upper half at 4 and 7. Rev C, by contrast, has a single four-word table (Good, Good to Soft, Soft, Heavy) with no surface input at all. The phrase "GOOD TO SOFT" exists in the Rev C ROM and is absent from the Rev D ROM.

Myth #2

Beer is a food, feed it for a boost

The Claim

"Draft Beer and Black Draft Beer are real foods in Derby Owners Club. Feed your horse beer and it works like any other treat: it raises its stats."

Where it shows up: stable and breeding chat, where beer splits the room. One camp says it is just a gag with no function, the other swears it boosts the horse.

The Evidence

Both halves of the argument are half right, so we tested each one against the ROM. The food list holds 45 items, and beer is two of them:

Food IDNameRecordGrowth payload
0Carrotnormal 44-byte recordhas stat points
36 / 37Korean Ginseng / Largenormal record2,2,2,2,2,2 doubles to 4,4,4,4,4,4
43Draft Beernormal record0,0,0,0,0,0
44Black Draft Beernormal record0,0,0,0,0,0

Beer is a genuine food, not a gag. Each food is a 44-byte record with a name, an artwork pointer, and a block of stat values. Draft Beer and Black Draft Beer are complete, well-formed records, and the routine that looks a food up by its number is plain arithmetic with no "is this a real food" check. So the game reaches beer exactly the way it reaches a carrot.

But beer boosts nothing. The six stat-growth bytes in every food record are what a feed actually adds to the horse. We know these six bytes are the growth values because the "Large" version of any food carries the same six numbers scaled up: Korean Ginseng is 2,2,2,2,2,2 and Large Korean Ginseng is exactly 4,4,4,4,4,4. Beer's six bytes are all zero. It is the only food in the game with an all-zero payload. Feeding beer adds nothing to any stat.

Instead, beer is special-cased for a reaction. The stable code singles out beer by ID in three places (grouped with bananas as novelty items). Rather than running the normal feed-and-grow path, beer sets a small reaction state (Draft Beer and Black Draft Beer each flag a different one) and pulls from its own dedicated table of sequence pointers. In other words the developers wrote special code so beer does something on screen, just not a stat gain.

So is beer actually offered to you in this version? That is a separate system from the stat values, and it is where the trail goes cold in the ROM. Whether a food shows up in your feed choices is decided by a small runtime table the game builds when a session starts: the offer routine reads that table per food and offers the food when its value clears a threshold. We traced that table all the way back to the code that fills it, and found two things. First, there is no beer-specific "off" switch anywhere: not in beer's record, not in the lookup, not in the availability data, and not in the table's setup code. Beer is handled by the same general machinery as every other food. Second, the table is not a fixed list in the ROM; the game computes it at runtime. That means the one remaining question, does beer's entry land in the offerable range in this build, cannot be answered by reading the ROM alone. It has to be read from the game while it runs.

Show the full ROM proof (offsets and disassembly)

All addresses are reproducible in the standard drbyocwc ROM set: World Edition Rev C program ROM epr-22336c.ic22, CRC32 50053F82. Sega Naomi SH-4; the image loads at 0x0C020000, so file offsets and RAM addresses line up.

The food table is at 0x0C186A7C, 45 entries of 44 bytes. Each record: name (24 bytes), artwork pointer (+0x18), then the stat-growth bytes at +0x1C through +0x21. The lookup at 0x0C09AD3E is ungated arithmetic, so any ID resolves:

0x0C09AD3E  mov   #44,r0       ; record stride
0x0C09AD44  muls.w r0,r4        ; id * 44
0x0C09AD4A  add   r3,r0         ; + table base, no range check
0x0C09AD4C  rts

The six growth bytes are the effect. Read across all 45 records, the +0x1C..+0x21 bytes are what differs by food, and the Large variants are the small ones scaled up:

id 36 KOREAN GINSENG        1C..21 = 02 02 02 02 02 02
id 37 LARGE KOREAN GINSENG  1C..21 = 04 04 04 04 04 04   ; exact double
id  0 CARROT                1C..21 = 02 00 00 00 00 00
id 43 DRAFT BEER            1C..21 = 00 00 00 00 00 00   ; all zero
id 44 BLACK DRAFT BEER      1C..21 = 00 00 00 00 00 00   ; all zero

Beer is special-cased by ID at three stable-code sites (each tests both 43 and 44, with no food-count bound, so these are beer-only branches):

0x0C0A4110  draw path  : beer uses hardcoded display offsets
                         (0x764 / 0x790) into a separate buffer,
                         not the normal table + id*44 indexing
0x0C0A5B44  feed path  : bananas (41/42) + beer (43/44) share a
                         branch; beer writes a reaction state at
                         0x0C21A4F4 (draft=1, black draft=2)
0x0C0A5FA0  feed path  : beer indexes its own pointer tables at
                         0x0C187E60 / 0x0C187E8C (its reaction
                         sequence), bypassing normal handling

The feed routine never reads the growth bytes for beer; it runs the reaction path above. Normal foods fall through to the usual grow path.

The offer gate (the access question). Whether a food is offered is decided here, independently of the growth bytes:

0x0C04806C  offer builder : for food 0..44, read the runtime
                          array at 0x0C21D738 + food*4, look it up
                          in the availability table, and offer the
                          food when the result is < 3
0x0C09AD50  lookup        : reads AVAIL[value*16 + mode_column]
0x0C1467FC  AVAIL table   : 16 bytes per row; beer's rows are
                          normal and offerable, not gated

Who fills the runtime array (this was the wall). The array at 0x0C21D738 is care-state base 0x0C21D6B8 + 0x80. It is filled by the care-state init:

0x0C03563C  init fill loop : writes 0x0C21D738[index] for the
                           first 40 foods; each entry is the
                           return of a runtime divide routine
                           (0x0C0C211C), not a value copied from
                           a fixed table in the ROM
beer entries  : DRAFT BEER       -> 0x0C21D7E4  (0x0C21D738 + 43*4)
                BLACK DRAFT BEER  -> 0x0C21D7E8  (0x0C21D738 + 44*4)

Because the values are computed live, the ROM does not state beer's final offer value. Reading those two RAM addresses while the game runs is what closes it.

Verdict: Open, needs more analysis

Beer is a real, fully coded food with no stat effect. Whether it is actually served to you in this build is still open, and you can help close it.

Here is what the ROM settles for certain. Beer is not a gag: Draft Beer and Black Draft Beer are complete, normal food records, and the game even writes special code just for them. And beer boosts nothing: its six growth values are all zero, the only food in the game like that. There is also no beer-specific "off" switch anywhere we looked.

What is still open is whether beer actually appears in your feed choices in World Edition Rev C. The game builds that decision from a small table it computes while running, not from a fixed list in the ROM, so reading the cartridge alone cannot answer it. This is where a community hand would finish the job:

  • Easiest, just play. Across different game modes and regions, watch whether Draft Beer or Black Draft Beer ever show up as a food you can pick. Note when and where they do or do not appear, and share it.
  • Read it live in Flycast. While a raising session is running, read NAOMI system RAM at 0x0C21D7E4 (Draft Beer) and 0x0C21D7E8 (Black Draft Beer). A value under 3 means the game considers that beer offerable in the current mode.
  • Finish the ROM side. The per-food offer values come out of a divide routine at 0x0C0C211C filled from 0x0C03563C. Working out its inputs would give a static answer for beer without needing a live read.
  • Name the six stats. The six growth bytes at record offset +0x1C..+0x21 are applied to the horse by the training code. Mapping each byte to an on-screen stat (Speed, Stamina, and so on) would tell us exactly what beer gives zero of.

Everything above is reproducible from epr-22336c.ic22 (CRC32 50053F82). If you take any of these further, bring it to the Discord or the Facebook group below and it will be folded in here with credit.

Have a myth to test?

Heard a claim about Derby Owners Club and want it settled from the ROM? Bring it to the community and it may end up here, tested and answered.

Join the Discord   |   Facebook Group