define game:flatten-bdcff(cave-idx level-idx)
  ;define calc-hashe-simple()
  ;  define otype
  ;  define w option-ref('width)
  ;  define h option-ref('height)
  ;  define bmap make-bytevector({w * h * 2})
  ;  define idx 0
  ;  while {idx <> {w * h}}
  ;    {otype := field-otype-ref({idx mod w} {idx div w})}
  ;    {bmap[{idx * 2}] := bit-and(otype #xFF)}
  ;    {bmap[{{idx * 2} + 1}] := bit-and(bit-shr(otype) #xFF)}
  ;    inc! idx
  ;  gset! cave-hash-u32 ::joaat:calc-hash(bmap)
  ;  bytevector-resize! bmap 0
  ;;
  ;; map hash calculated from the width, height, and object names, separated by zero byte
  define calc-hashes()
    define w option-ref('width)
    define h option-ref('height)
    define jh ::joaat:new(#x29A)
    define jc ::ripemd160:new()
    define oname
    ;;
    define hash-int(n)
      assert between?(n -65536 #xFFFFFF)
      ;; joaat
      ::joaat:put! jh bit-and(n #xFF)
      ::joaat:put! jh bit-and(bit-sar(n 8) #xFF)
      ::joaat:put! jh bit-and(bit-sar(n 16) #xFF)
      ;; ripe
      ::ripemd160:put! jc bit-and(n #xFF)
      ::ripemd160:put! jc bit-and(bit-sar(n 8) #xFF)
      ::ripemd160:put! jc bit-and(bit-sar(n 16) #xFF)
    ;;
    define hash-bool(n)
      cond
        n
          ::joaat:put! jh 1
          ::ripemd160:put! jc 1
        else
          ::joaat:put! jh 0
          ::ripemd160:put! jc 0
    ;;
    define hash-string(n)
      ::joaat:put! jh n
      ::ripemd160:put! jc n
      ;; final 0
      ::joaat:put! jh 0
      ::ripemd160:put! jc 0
    ;;
    define hash-symbol(n)
      ::joaat:put! jh symbol->string(n)
      ::ripemd160:put! jc symbol->string(n)
    ;;
    define hash-otype(n)
      hash-symbol otype-name(n)
    ;;
    define hash-option-value(value type)
      case type
        (BOOLEAN)     hash-bool(value)
        (INTEGER)     hash-int(value)
        (STRING)      hash-int(value)
        (ELEMENT)     hash-otype(value)
        (PROBABILITY) hash-int(value)
        (RATIO)       hash-int(value)
    ;;
    define hash-options()
      define name
      define value
      define props
      define type
      define opt-vv
      define vec-idx
      define it ::dict:iter:new(default-cave-options)
      while ::dict:iter:next(it)
        {name := ::dict:iter:key(it)}
        {value := car(::dict:iter:value(it))}
        {props := option-props-ref(name)}
        if {true? lax-cdr(assq(cave-hash: props))}
          then
            ;printf "key=%o\n" name
            {type := lax-cdr(assq(type: props))}
            if {null? type}
              then
                {opt-vv := (if {vector? value} value[0] value)}
                ;printf "%o: type: %o\n" opt-vv ::SYSTEM:type-name(opt-vv)
                cond
                  {boolean? opt-vv} {type := 'BOOLEAN}
                  {fixnum? opt-vv} {type := 'INTEGER}
                  {string? opt-vv} {type := 'STRING}
            ;; hash it
            hash-symbol name
            if {vector? value}
              iterate
                init {vec-idx := 0}
                repeat {vec-idx <> vector-length(value)}
                  hash-option-value value[vec-idx] type
                  inc! vec-idx
                else #void
              else
                hash-option-value value type
    ;;
    ;; map dimensions
    hash-int w
    hash-int h
    ;; map bitmap
    define idx 0
    while {idx <> {w * h}}
      hash-otype field-otype-ref({idx mod w} {idx div w})
      inc! idx
    ;; map hashes
    gset! map-cave-hash-u32 ::joaat:finish!(::joaat:clone(jh))
    gset! map-cave-hash-str (::ripemd160:to-string (::ripemd160:finish! ::ripemd160:clone(jc)))
    ;;
    hash-options()
    ;; done
    gset! full-cave-hash-u32 ::joaat:finish!(jh)
    gset! full-cave-hash-str (::ripemd160:to-string (::ripemd160:finish! jc))
  ;;
  define caveopt bdcff-loader:get-cave-options(cave-idx)
  if use-max-map-size
    new-cave max-field-width max-field-height caveopt  ; this is done deliberately to make the engine slower!
    new-cave #f #f caveopt
  ;;
  gset! cave-options-override bdcff-loader:get-cave-options(cave-idx)
  define cmap option-ref('map)
  define clegend option-ref('map-legend)
  ;;
  define find-legend-default-c0(c0)
    define xx ::dict:find(bdcff-legend-map number->string(c0))
    assert {fixnum? xx} string-append("cannot find map legend char \"" c0 "\"")
    xx
  ;;
  define find-legend-c0(c0)
    define xx assq(c0 clegend)
    if {pair? xx}
      cdr xx
      find-legend-default-c0(c0)
  ;;
  define bad-objects #nil
  ;;
  define report-bad-object(def)
    gset! game:has-bad-objects #t
    define cnt assq(edef:index(def) bad-objects)
    cond
      {pair? cnt}
        cdr-set! cnt {cdr(cnt) + 1}
      else
        {bad-objects := cons(cons(edef:index(def) 1) bad-objects)}
  ;;
  define print-bad-objects()
    define list reverse!(bad-objects) ;; in order of appearance
    cond
      {pair? list}
        printf "ERROR! found some bad objects, the cave is prolly unplayable!\n"
        printf "=== BAD OBJECTS LIST ===\n"
        while {pair? list}
          printf "  %o: %o time%s\n"
            ::enum:find-value(etype caar(list))
            cdar(list)
            (if {cdar(list) = 1} "" "s")
          {list := cdr(list)}
      else #void
  ;;
  define warn-bad-objects()
    define list reverse!(bad-objects) ;; in order of appearance
    cond
      {pair? list}
        bdcff-loader:warning "found some bad objects, the cave is prolly unplayable!"
        while {pair? list}
          bdcff-loader:warning
            string-append("object \"" ::enum:find-value(etype caar(list))
                          "\": " number->string(cdar(list))
                          " time" (if {cdar(list) = 1} "" "s"))
          {list := cdr(list)}
      else #void
  ;;
  define check-bad-options()
    if game:option-ref('gravity-switch-active)
      then
        if bdcff-loader:print-warnings?()
          printf "ERROR: gravitation change is not supported! this is stupid.\n"
        gset! has-bad-objects #t
        ::option-remove! cave-options-override 'gravity-switch-active
    if {not game:option-ref('border-scan-first-and-last)}
      then
        if bdcff-loader:print-warnings?()
          then
            printf "WARNING: \"BorderProperties.scan=false\" is not supported!\n"
            printf "WARNING: Miho Dash always scans the whole room.\n"
        ;gset! has-bad-objects #t ;; it should not be fatal, though
        ::option-remove! cave-options-override 'border-scan-first-and-last
    ;if game:option-ref('lineshift)
    ;  then
    ;    printf "WARNING: \"BorderProperties.lineshift=true\" is not supported!\n"
    ;    printf "WARNING: Miho Dash always uses the proper wraparound mode.\n"
    ;    ;gset! has-bad-objects #t ;; it should not be fatal, though
    ;    ::option-remove! cave-options-override 'lineshift
    ;if {not game:option-ref('wraparound-objects)}
    ;  then
    ;    printf "WARNING: \"BorderProperties.objectwraparound=false\" is not supported!\n"
    ;    printf "WARNING: Miho Dash always uses the proper wraparound mode.\n"
    ;    ;gset! has-bad-objects #t ;; it should not be fatal, though
    ;    ::option-remove! cave-options-override 'wraparound-objects
  ;;
  ;; spawn new object
  define bdcff-spawn-at(x y otype)
    if {{not {false? otype}} and
        between?(x 0 {option-ref('width) - 1}) and
        between?(y 0 {option-ref('height) - 1})}
      then
        define ota abs(otype)
        ;; never spawned by the engine, but cound be used in BDCFF (for some unknown reason).
        cond
          {{ota = etype:O_OUTBOX_CLOSED} or {ota = etype:O_OUTBOX_OPEN}}
            {otype := etype:O_OUTBOX}  ;; no need to delay it
          {{ota = etype:O_PRE_INVIS_OUTBOX} and option-ref('reveal-invisible-outbox)}
            {otype := etype:O_PRE_OUTBOX}  ;; no need to delay it
          else #void
        ;printf "x=%o; y=%o; otype=%o\n" x y otype
        define def spawn-at-internal(x y otype)
        ;printf "%s %o\n" edef:name(def) edef:prop?(def 'P_BAD_OBJECT)
        cond
          edef:prop?(def 'P_BAD_OBJECT)
            report-bad-object def
            ;spawn-at x y etype:O_UNKNOWN
            spawn-at x y etype:O_QUESTION_MARK
          edef:prop?(def 'P_PLAYER)
            player-found(x y #t)
          {abs(otype) = etype:O_INBOX}
            player-found(x y #f)
          else
            #void
  ;;
  define blit-map()
    let row-loop <* map-str car(cmap) \\ rest cdr(cmap) \\ y0 0 *>
      let col-loop <* idx 0 \\ map-str map-str \\ x0 0 \\ y0 y0 *>
        cond
          {idx <> string-length(map-str)}
            bdcff-spawn-at x0 y0 find-legend-c0({map-str[idx]})
            col-loop {idx + 1} map-str {x0 + 1} y0
          else #void
      if {pair? rest}
        row-loop car(rest) cdr(rest) {y0 + 1}
  ;;
  define initial-random-fill()
    ; generate random fill.
    ; start from row 1 (0 skipped), and fill also the borders on left and right hand side,
    ; as c64 did. this way works the original random generator the right way.
    ; also, do not fill last row, that is needed for the random seeds to be correct
    ; after filling! predictable slime will use it.
    define x
    define y
    define rr
    define rf-idx
    define otype
    define fills option-ref-nounwrap('gen-random-fill)
    define probs option-ref-nounwrap('gen-random-prob)
    define w {option-ref('width) - 1}
    define h {option-ref('height) - 1}
    iterate
      init {y := 1}
      repeat {y < h}
        iterate
          init {x := 0}
          repeat {x <= w}
            {rr := pprng-u8()}
            ; select the element to draw the way it was done on c64
            iterate
              init
                {otype := option-ref('gen-initial-fill)}
                {rf-idx := 0}
              repeat {rf-idx <> 4}
                if {rr < probs[rf-idx]} {otype := fills[rf-idx]}
                inc! rf-idx
              else #void
            bdcff-spawn-at x y otype
            inc! x
          else #void
        inc! y
      else #void
    ; draw initial border
    {otype := option-ref('gen-initial-border)}
    iterate
      init {y := 0}
      repeat {y <= h}
        bdcff-spawn-at 0 y otype
        bdcff-spawn-at w y otype
        inc! y
      else #void
    iterate
      init {x := 0}
      repeat {x <= w}
        bdcff-spawn-at x 0 otype
        bdcff-spawn-at x h otype
        inc! x
      else #void
  ;;
  define process-object(objlist)
    define wrap-x(x y)
      cond
        option-ref('wraparound-objects)
          define w option-ref('width)
          define h option-ref('height)
          cond
            option-ref('lineshift)
              while {negative? x}
                dec! y
                inc! x w
              while {x >= w}
                inc! y
                dec! x w
              if between?(y 0 {h - 1})
                x
                -666
            else
              while {negative? x} inc!(x w)
              {x mod w}
        else x
    ;;
    define wrap-y(x y)
      cond
        option-ref('wraparound-objects)
          define w option-ref('width)
          define h option-ref('height)
          cond
            option-ref('lineshift)
              while {negative? x}
                dec! y
                inc! x w
              while {x >= w}
                inc! y
                dec! x w
              if between?(y 0 {h - 1})
                y
                -666
            else
              while {negative? y} inc!(y h)
              {y mod h}
        else y
    ;;
    define obj-read-at(x y)
      {x := wrap-x(x y)}
      {y := wrap-y(x y)}
      cond
        {between?(x 0 {option-ref('width) - 1}) and between?(y 0 {option-ref('height) - 1})}
          field-otype-ref x y
        else
          #f
    ;;
    define obj-spawn-at(x y otype)
      ;; wrapping
      {x := wrap-x(x y)}
      {y := wrap-y(x y)}
      bdcff-spawn-at x y otype
    ;;
    cond
      {null? objlist}
        #void
      {false? caar(objlist)[cave-level]}
        process-object cdr(objlist)
      else
        cdar(objlist)(obj-spawn-at obj-read-at cave-level option-ref('width) option-ref('height)
                      wrap-x wrap-y)
        process-object cdr(objlist)
  ;;
  gset! cave-index cave-idx
  gset! cave-level level-idx
  gset! player-count 0
  gset! player-hatched #f
  ;; level speed
  ;if {use-fixed-level-speed and {not {null? cave-options-override}}}
  ;  option-lax-remove! cave-options-override 'level-speed
  gset! cave-step-time option-ref('level-speed)
  if use-fixed-level-speed
    gset! cave-step-time fixed-level-speed
  ;; setup parameters
  gset! viewport-x option-ref('viewport-x)
  gset! viewport-y option-ref('viewport-y)
  gset! viewport-w option-ref('viewport-w)
  gset! viewport-h option-ref('viewport-h)
  gset! cave-name option-ref('name)
  gset! cave-time option-ref('level-time)
  gset! diamonds-required option-ref('diamonds-required)
  gset! diamond-value option-ref('diamond-value)
  gset! extra-diamond-value option-ref('extra-diamond-value)
  ;; various timers
  gset! biter-delay-frame option-ref('biter-delay-frame)
  gset! biters-wait-frame 0
  gset! replicator-delay-frame option-ref('replicator-delay-frame)
  gset! replicators-wait-frame 0
  ;; amoeba setup
  gset! amoeba-growth-prob option-ref('amoeba-growth-prob)
  gset! amoeba-fast-growth-prob option-ref('amoeba-fast-growth-prob)
  gset! amoeba-threshold option-ref('level-amoeba-threshold)
  gset! amoeba-time {option-ref('level-amoeba-time) * 5} ;; 5 frames per second
  gset! amoeba-timer-active #f
  ;; creature direction timer
  gset! creatures-backwards option-ref('creatures-backwards)
  gset! creature-reverse-timer-init max(0 option-ref('creatures-direction-auto-change-time))
  gset! creature-reverse-timer creature-reverse-timer-init
  gset! creature-reverse-timer-active {positive? creature-reverse-timer-init}
  ;; magic wall setup
  gset! magic-wall-initial-timer {option-ref('level-magic-wall-time) * 5} ;; 5 frames per second
  if {{zero? magic-wall-initial-timer} and option-ref('magic-timer-zero-is-infinite)}
    gset! magic-wall-initial-timer (fixnum-max)
  gset! magic-timer-active {not option-ref('magic-timer-wait-for-hatching)}
  ;; hatch time
  if {{not {null? cave-options-override}} and
      {::has-option?(cave-options-override 'level-hatching-delay-frame)}}
    gset! hatch-time option-ref('level-hatching-delay-frame)
    gset! hatch-time {{option-ref('level-hatching-delay-time) * 1000} div cave-step-time}
  ;; set initial random seed
  set-pprng-seed-u16(option-ref('level-rand))
  ;; blit the map
  if {pair? cmap}
    blit-map()
    initial-random-fill()
  process-object(option-ref('cave-objects))
  ;;
  update-player-history()
  calc-hashes()
  ;;
  if bdcff-loader:print-warnings?()
    print-bad-objects()
    warn-bad-objects()
  check-bad-options()
