16-bit addressing modes have bx and bp as "base" registers, and di and si as "index" registers. Valid effective addresses can include an (optional) offset, plus an (optional) base register, plus an (optional) index register. Switch to one of those registers, and it should work.
32-bit addressing modes are much more flexible. An (optional) offset, plus an (optional) base register, plus an (optional) "index" register, which may be multiplied by a "scale" of 2, 4, or 8. *Any* register can be used for "base" and "index", except that esp can't be used as "index".
Using 32-bit addressing modes in 16-bit code works. You *could* do "mov dx, [state + ecx -1]", even in 16-bit code. The total offset can't exceed the "segment limit" - ordinarily 0FFFFh (in "real dos" we can change this - in a Windows "dos box", no). The upper word of 32-bit registers is "probably" clear in 16-bit code, but sometimes not, so "xor ecx, ecx" (any and all registers you plan to use in this way) somewhere near the top of your program would be "safer".
I think your best solution would be to use bx, di, or si - bp is "special", in that it defaults to [ss:bp], instead of the usual default of [ds:???]. (*all* x86 addresses involve a segment register - often we can ignore 'em)
Best,
Frank