# Headless, deterministic Doom (doomgeneric + Freedoom).
#
#   make -C doom native          -> doom/build/libdoom.{dylib,so}
#   make -C doom wasm            -> public/doom/doom-<hash>.{js,wasm}
#   make -C doom source-archive  -> public/doom/source/doom-src-<hash>.tar.gz
#
# native: only a C compiler is required (clang or gcc); no SDL, no audio.
# wasm:   needs emcc on PATH (source the emsdk env first).
#
# -ffp-contract=off is load-bearing: the 2D view uses IEEE doubles and the
# WASM build has no FMA; forbidding contraction keeps every frame bit-
# identical between native and WASM (see doom/tests/test_parity_wasm.mjs).

CC      ?= cc
EMCC    ?= emcc
UNAME   := $(shell uname)
BUILD   := build
ENGINE  := engine
API     := api
PUB     := ../public/doom

CFLAGS  += -O2 -fPIC -ffp-contract=off -std=gnu99 -w \
           -DDOOM_HEADLESS -DNORMALUNIX -DLINUX \
           -DDOOMGENERIC_RESX=320 -DDOOMGENERIC_RESY=200 \
           -I$(ENGINE) -I$(API)

ifeq ($(UNAME),Darwin)
LIB     := $(BUILD)/libdoom.dylib
SHARED  := -dynamiclib
else
LIB     := $(BUILD)/libdoom.so
SHARED  := -shared
endif

# platform backends and SDL/Allegro sound modules are excluded on purpose;
# dummy.c provides the net-client stubs.
EXCLUDE := doomgeneric_allegro doomgeneric_emscripten doomgeneric_linuxvt \
           doomgeneric_sdl doomgeneric_soso doomgeneric_sosox \
           doomgeneric_win doomgeneric_xlib \
           i_allegromusic i_allegrosound i_sdlmusic i_sdlsound \
           i_cdmus mus2mid

ENGINE_SRCS := $(filter-out $(addprefix $(ENGINE)/,$(addsuffix .c,$(EXCLUDE))),\
                 $(wildcard $(ENGINE)/*.c))
API_SRCS    := $(wildcard $(API)/*.c)
OBJS        := $(patsubst %.c,$(BUILD)/%.o,$(notdir $(ENGINE_SRCS) $(API_SRCS)))


WASMFLAGS := -O2 -ffp-contract=off -std=gnu99 -w \
             -DDOOM_HEADLESS -DNORMALUNIX -DLINUX \
             -DDOOMGENERIC_RESX=320 -DDOOMGENERIC_RESY=200 \
             -I$(ENGINE) -I$(API) \
             --no-entry \
             -sMODULARIZE=1 -sEXPORT_ES6=1 -sEXPORT_NAME=createDoomEngine \
             -sENVIRONMENT=web,node \
             -sFILESYSTEM=1 -sALLOW_MEMORY_GROWTH=1 \
             -sINITIAL_MEMORY=67108864 -sSTACK_SIZE=1048576 \
             -sASSERTIONS=0

WASM_DIR := $(BUILD)/wasm

native: $(LIB)

$(LIB): $(OBJS)
	$(CC) $(SHARED) $(OBJS) -o $@ -lm

$(BUILD)/%.o: $(ENGINE)/%.c | $(BUILD)
	$(CC) $(CFLAGS) -c $< -o $@

$(BUILD)/%.o: $(API)/%.c | $(BUILD)
	$(CC) $(CFLAGS) -c $< -o $@

$(BUILD):
	mkdir -p $(BUILD)

# ---- WASM -----------------------------------------------------------------
# One-shot emcc build (no object files leave doom/build/). The .wasm file is
# named with its own content hash; the .js (which references the .wasm by
# name) is patched and then named with its content hash. Stale hashed builds
# in public/doom/ are removed, so exactly one engine build is checked in.
wasm: $(ENGINE_SRCS) $(API_SRCS)
	@command -v $(EMCC) >/dev/null || { \
	  echo "emcc not found: source ~/.cache/emsdk/emsdk_env.sh first"; exit 1; }
	mkdir -p $(WASM_DIR)
	$(EMCC) $(WASMFLAGS) \
	  "-sEXPORTED_FUNCTIONS=['_dgn_init','_dgn_new_game','_dgn_step','_dgn_step_cmd','_dgn_spawn_thing','_dgn_remove_thing','_dgn_list_things','_dgn_set_player','_dgn_give','_dgn_kill_thing','_dgn_damage_player','_dgn_heal_player','_dgn_set_ammo','_dgn_take_ammo','_dgn_set_weapon','_dgn_sectors_live','_dgn_lines_live','_dgn_frame3d','_dgn_frame2d','_dgn_frame2d_preset','_dgn_state_hash','_dgn_eval_state_json','_dgn_level_geometry_json','_dgn_free','_dgn_check_position','_dgn_try_step','_dgn_try_path','_dgn_set_sector_floor','_dgn_save_state','_dgn_load_state','_dgn_free_state','_dgn_state_bytes','_dgnw_state_hash_hex','_dgnw_flags','_dgnw_leveltime','_dgnw_check_position','_dgn_arena_reset','_dgn_arena_status','_dgn_route_set','_dgn_route_clear','_dgn_route_status','_dgn_route_set_exit','_dgn_handover_mode','_dgn_handover_act','_dgn_handover_status','_malloc','_free']" \
	  "-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','FS','HEAPU8','HEAP32','HEAPF64']" \
	  $(ENGINE_SRCS) $(API_SRCS) -o $(WASM_DIR)/doom.js
	@WH=$$(shasum -a 256 $(WASM_DIR)/doom.wasm | cut -c1-10); \
	sed "s/doom\.wasm/doom-$$WH\.wasm/g" $(WASM_DIR)/doom.js > $(WASM_DIR)/doom-hashed.js; \
	JH=$$(shasum -a 256 $(WASM_DIR)/doom-hashed.js | cut -c1-10); \
	rm -f $(PUB)/doom-*.wasm $(PUB)/doom-*.js; \
	cp $(WASM_DIR)/doom.wasm $(PUB)/doom-$$WH.wasm; \
	cp $(WASM_DIR)/doom-hashed.js $(PUB)/doom-$$JH.js; \
	echo "wasm: public/doom/doom-$$JH.js + doom-$$WH.wasm"

# ---- GPL source archive ----------------------------------------------------
# Reproducible: sorted file list (git ls-files is sorted), staged copies with
# a fixed mtime (portable: bsdtar has no --mtime), fixed owner, gzip -n.
# Running it twice gives the same sha256. Contains exactly the tree that was
# built: the modified GPL engine, our API/binding code, the Makefile, the
# licence manifest and the licence texts.
SRC_LIST  := $(BUILD)/src-files.txt
SRC_STAGE := $(BUILD)/src-stage
SRC_TGZ   := $(BUILD)/doom-src.tar.gz

source-archive:
	mkdir -p $(BUILD) $(PUB)/source
	cd .. && git ls-files \
	  'doom/engine' 'doom/api' 'doom/Makefile' 'doom/actions.json' \
	  'doom/python' 'doom/tests' 'doom/README.md' 'doom/LICENSES.md' \
	  'public/doom/LICENSES' > doom/$(SRC_LIST)
	@test -s $(SRC_LIST) || { echo "git ls-files empty - commit first"; exit 1; }
	rm -rf $(SRC_STAGE)
	@while IFS= read -r f; do \
	  mkdir -p "$(SRC_STAGE)/$$(dirname "$$f")"; \
	  cp "../$$f" "$(SRC_STAGE)/$$f"; \
	done < $(SRC_LIST)
	find $(SRC_STAGE) -type f -exec touch -t 200001010000 {} +
	cd $(SRC_STAGE) && tar --uid 0 --gid 0 --uname root --gname root \
	  --numeric-owner -cf - -T ../src-files.txt | gzip -n -9 \
	  > ../doom-src.tar.gz
	@H=$$(shasum -a 256 $(SRC_TGZ) | cut -c1-16); \
	rm -f $(PUB)/source/doom-src-*.tar.gz; \
	cp $(SRC_TGZ) $(PUB)/source/doom-src-$$H.tar.gz; \
	echo "source archive: public/doom/source/doom-src-$$H.tar.gz"; \
	shasum -a 256 $(PUB)/source/doom-src-$$H.tar.gz

# ---- arena map -------------------------------------------------------------
# AJBSP 1.05 (GPL-2.0) built from the pinned source tarball into
# doom/build/tools/. The binary is not shipped. `make maps` always
# rewrites public/doom/maps/arena1.wad; two runs are byte-identical.
AJBSP_URL := https://gitlab.com/andwj/ajbsp/-/archive/v1.05/ajbsp-v1.05.tar.gz
AJBSP_SHA := 7687671702668efb37b09fcdd3dbe68d7b0dd27e79657d51d6998924eca76ea1
TOOLS     := $(BUILD)/tools
AJBSP_TGZ := $(TOOLS)/ajbsp-1.05.tar.gz
AJBSP_SRC := $(TOOLS)/ajbsp-1.05
AJBSP     := $(TOOLS)/ajbsp

$(AJBSP):
	mkdir -p $(TOOLS)
	curl -fsSL -o $(AJBSP_TGZ) $(AJBSP_URL)
	echo "$(AJBSP_SHA)  $(AJBSP_TGZ)" | shasum -a 256 -c -
	rm -rf $(AJBSP_SRC)
	mkdir -p $(AJBSP_SRC)
	tar -xzf $(AJBSP_TGZ) -C $(AJBSP_SRC) --strip-components=1
	$(MAKE) -C $(AJBSP_SRC)
	cp $(AJBSP_SRC)/ajbsp $(AJBSP)

maps: $(AJBSP)
	mkdir -p $(PUB)/maps $(BUILD)
	python3 maps/build_arena.py $(BUILD)/arena1.raw.wad
	$(AJBSP) -n -m E1M1 -o $(PUB)/maps/arena1.wad $(BUILD)/arena1.raw.wad

clean:
	rm -rf $(BUILD)

.PHONY: native wasm source-archive clean maps
