diff --git a/.gitignore b/.gitignore
index f935cf5..95a02ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ Release/
 
 # xmake 缓存
 .xmake/
+.cache/
 
 # clangd 编译数据库（生成产物）
 compile_commands.json
diff --git a/include/data_storage.h b/include/data_storage.h
index 48b7846..a1db9eb 100644
--- a/include/data_storage.h
+++ b/include/data_storage.h
@@ -16,8 +16,8 @@ namespace write{
         bool id();
         bool name(int id, const std::string& new_name);
         bool gender(int id, const std::string& new_gender);
-        bool old_point(int id, const long long& new_old_point);
-        bool point(int id, const long long& new_point);
+        bool old_score(int id, const long long& new_old_score);
+        bool score(int id, const long long& new_score);
         bool old_rank(int id, const int& new_old_rank);
         bool rank(int id, const int& new_rank);
     }
diff --git a/old/main.cpp b/old/main.cpp
index 1d06a16..dc4e98a 100644
--- a/old/main.cpp
+++ b/old/main.cpp
@@ -21,8 +21,6 @@
 #include <vector>
 #include <string>
 #include <algorithm>
-#include <limits>
-#include <climits>
 #include <sstream>
 #include <map>
 #include <cstdint>
diff --git a/src/data_storage.cpp b/src/data_storage.cpp
new file mode 100644
index 0000000..be3906d
--- /dev/null
+++ b/src/data_storage.cpp
@@ -0,0 +1,236 @@
+// 业务存储层：把 FileStore 引擎接到 data_storage.h 的字段级 API
+// 班级语义：read::xxx(class) 激活班级（学生/规则/礼物各走独立表单例）；
+//           write::xxx 作用于当前激活班级（无班级参数的签名按当前班级处理）
+// 持久化语义：write 只改内存 + 置脏标记，由调用方在动作边界显式 save()。
+// 注意：include 必须放在 import 之前——MSVC 下消费 TU 在 import 后再 include 模块全局片段已含
+// 的 STL 头（<string>/<fstream> 等）会触发 C2572 默认模板参数重复定义
+#include "data_storage.h"
+
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <map>
+#include <stdexcept>
+#include <string>
+
+import storage;
+
+namespace
+{
+	std::string path_for(const std::string &class_name, const char *stem)
+	{
+		if (class_name.empty() || class_name == "default")
+			return std::string(stem) + ".dat";
+		return std::string(stem) + "_" + class_name + ".dat";
+	}
+
+	// 三张表各自的单例门面：学生 = FileInstance，规则/礼物 = 同构 Record 的不同类型
+	using RuleStore = points::FileStore<points::RuleRecord>;
+	using GiftStore = points::FileStore<points::GiftRecord>;
+
+	template<typename Store>
+	Store &ensure_store(Store * /*tag*/, const std::string &class_name, const char *stem)
+	{
+		auto path = path_for(class_name, stem);
+		try
+		{
+			auto &s = Store::get();
+			if (s.path() != path)
+				s.switch_to(path); // 切班级：脏则自动落盘
+			return s;
+		} catch (const std::logic_error &)
+		{ // 未初始化
+			Store::init(path);
+			return Store::get();
+		}
+	}
+
+	points::FileInstance &ensure_students(const std::string &class_name)
+	{
+		return ensure_store(
+			static_cast<points::FileInstance *>(nullptr),
+			class_name,
+			"students");
+	}
+
+	RuleStore &ensure_rules(const std::string &class_name)
+	{
+		return ensure_store(static_cast<RuleStore *>(nullptr), class_name, "rules");
+	}
+
+	GiftStore &ensure_gifts(const std::string &class_name)
+	{
+		return ensure_store(static_cast<GiftStore *>(nullptr), class_name, "gifts");
+	}
+
+	// UTF-8 安全截断：不超过 max_bytes，且不在多字节序列中间切断
+	void copy_utf8_truncated(std::byte *dest, size_t max_bytes, const std::string &src)
+	{
+		size_t n = std::min(src.size(), max_bytes);
+		if (n < src.size())
+		{ // 需要截断：回退到完整字符边界
+			while (n > 0 && (static_cast<unsigned char>(src[n]) & 0xC0u) == 0x80u)
+				--n;
+		}
+		std::memcpy(dest, src.data(), n);
+		std::memset(dest + n, 0, max_bytes - n);
+	}
+
+	std::string current_class = "default"; // read::xxx 激活，write::xxx 消费
+}                                          // namespace
+
+namespace read
+{
+	void students(const std::string &class_name)
+	{
+		current_class = class_name;
+		ensure_students(class_name); // 加载即激活
+	}
+
+	void rules(const std::string &class_name)
+	{
+		current_class = class_name;
+		ensure_rules(class_name).load(); // 强制从盘重读
+	}
+
+	void gifts(const std::string &class_name)
+	{
+		current_class = class_name;
+		ensure_gifts(class_name).load();
+	}
+
+	std::map<std::string, std::string> config(const std::string & /*class_name*/)
+	{
+		std::map<std::string, std::string> kv;
+		std::ifstream                      fin("config.ini");
+		std::string                        line;
+		while (std::getline(fin, line))
+		{
+			auto pos = line.find('=');
+			if (pos == std::string::npos)
+				continue;
+			kv[line.substr(0, pos)] = line.substr(pos + 1);
+		}
+		return kv;
+	}
+} // namespace read
+
+namespace write
+{
+	namespace student
+	{
+		// 签名歧义：header 只声明了 bool id()，按"新建一个空学生并返回是否成功"实现
+		bool id()
+		{
+			ensure_students(current_class).add();
+			return true;
+		}
+
+		bool name(int id, const std::string &new_name)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			copy_utf8_truncated(r->name, sizeof(r->name), new_name);
+			fi.mark_dirty();
+			return true;
+		}
+
+		bool gender(int id, const std::string &new_gender)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			copy_utf8_truncated(r->gender, sizeof(r->gender), new_gender);
+			fi.mark_dirty();
+			return true;
+		}
+
+		bool old_score(int id, const long long &new_old_score)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			r->old_score = new_old_score;
+			fi.mark_dirty();
+			return true;
+		}
+
+		bool score(int id, const long long &new_score)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			r->score = new_score;
+			fi.mark_dirty();
+			return true;
+		}
+
+		bool old_rank(int id, const int &new_old_rank)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			r->old_rank = new_old_rank;
+			fi.mark_dirty();
+			return true;
+		}
+
+		bool rank(int id, const int &new_rank)
+		{
+			auto &fi = ensure_students(current_class);
+			auto *r  = fi.find(id);
+			if (!r) return false;
+			r->rank = new_rank;
+			fi.mark_dirty();
+			return true;
+		}
+	} // namespace student
+
+	namespace rule
+	{
+		// rule_num 1..N 映射到槽位 rule_num-1（引擎 add() 分配，槽位即编号）
+		bool desc(int rule_num, const std::string &new_desc)
+		{
+			auto &s = ensure_rules(current_class);
+			auto *r = s.find(static_cast<uint32_t>(rule_num - 1));
+			if (!r) return false;
+			copy_utf8_truncated(r->desc, sizeof(r->desc), new_desc);
+			s.mark_dirty();
+			return true;
+		}
+
+		bool delta(int rule_num, const int &new_delta)
+		{
+			auto &s = ensure_rules(current_class);
+			auto *r = s.find(static_cast<uint32_t>(rule_num - 1));
+			if (!r) return false;
+			r->delta = new_delta;
+			s.mark_dirty();
+			return true;
+		}
+	} // namespace rule
+
+	namespace gift
+	{
+		bool desc(int gift_num, const std::string &new_desc)
+		{
+			auto &s = ensure_gifts(current_class);
+			auto *r = s.find(static_cast<uint32_t>(gift_num - 1));
+			if (!r) return false;
+			copy_utf8_truncated(r->desc, sizeof(r->desc), new_desc);
+			s.mark_dirty();
+			return true;
+		}
+
+		bool delta(int gift_num, const int &new_delta)
+		{
+			auto &s = ensure_gifts(current_class);
+			auto *r = s.find(static_cast<uint32_t>(gift_num - 1));
+			if (!r) return false;
+			r->delta = new_delta;
+			s.mark_dirty();
+			return true;
+		}
+	} // namespace gift
+}     // namespace write
diff --git a/src/exceptions.ixx b/src/exceptions.ixx
new file mode 100644
index 0000000..03992c6
--- /dev/null
+++ b/src/exceptions.ixx
@@ -0,0 +1,15 @@
+//
+// Created by jimmy on 2026/8/4.
+//
+module;
+#include <stdexcept>
+
+export module exceptions;
+
+export namespace points
+{
+	struct file_format_error : std::runtime_error
+	{
+		using std::runtime_error::runtime_error;
+	};
+}
diff --git a/src/storage_management.cpp b/src/storage_management.cpp
new file mode 100644
index 0000000..88fb2c3
--- /dev/null
+++ b/src/storage_management.cpp
@@ -0,0 +1,104 @@
+module;
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <cstring>
+#include <string_view>
+
+module storage;
+import exceptions;
+
+namespace points
+{
+	// ---- Record（学生，161B）----
+	void Record::put(std::string &buf, const Record &r)
+	{
+		buf.push_back(static_cast<char>(r.flags));
+		auto put = [&buf](auto v)
+		{
+			buf.append(reinterpret_cast<const char *>(&v), sizeof(v));
+		};
+		put(r.id);
+		put(r.next);
+		buf.append(reinterpret_cast<const char *>(r.name), 64);
+		buf.append(reinterpret_cast<const char *>(r.gender), 64);
+		put(r.old_score);
+		put(r.score);
+		put(r.old_rank);
+		put(r.rank);
+	}
+
+	void Record::get(std::string_view s, size_t slot, Record &r)
+	{
+		const size_t off = slot * get_record_field_size();
+		if (s.size() < off + get_record_field_size())
+			throw file_format_error("Record slot out of range");
+		// 与 put 严格镜像：同序、同长度，跑指针逐个字段取
+		const char *p = s.data() + off;
+		r.flags       = static_cast<uint8_t>(*p);
+		++p;
+		memcpy(&r.id, p, sizeof(r.id));
+		p += sizeof(r.id);
+		memcpy(&r.next, p, sizeof(r.next));
+		p += sizeof(r.next);
+		memcpy(r.name, p, 64);
+		p += 64;
+		memcpy(r.gender, p, 64);
+		p += 64;
+		memcpy(&r.old_score, p, sizeof(r.old_score));
+		p += sizeof(r.old_score);
+		memcpy(&r.score, p, sizeof(r.score));
+		p += sizeof(r.score);
+		memcpy(&r.old_rank, p, sizeof(r.old_rank));
+		p += sizeof(r.old_rank);
+		memcpy(&r.rank, p, sizeof(r.rank));
+	}
+
+	void Record::clear(Record &r)
+	{
+		std::memset(r.name, 0, sizeof(r.name));
+		std::memset(r.gender, 0, sizeof(r.gender));
+		r.old_score = 0;
+		r.score     = 0;
+		r.old_rank  = 0;
+		r.rank      = 0;
+	}
+
+	// ---- RuleRecord（规则/礼物，77B；GiftRecord 继承）----
+	void RuleRecord::put(std::string &buf, const RuleRecord &r)
+	{
+		buf.push_back(static_cast<char>(r.flags));
+		auto put = [&buf](auto v)
+		{
+			buf.append(reinterpret_cast<const char *>(&v), sizeof(v));
+		};
+		put(r.id);
+		put(r.next);
+		buf.append(reinterpret_cast<const char *>(r.desc), 64);
+		put(r.delta);
+	}
+
+	void RuleRecord::get(std::string_view s, size_t slot, RuleRecord &r)
+	{
+		const size_t off = slot * rule_disk_size();
+		if (s.size() < off + rule_disk_size())
+			throw file_format_error("Rule slot out of range");
+		const char *p = s.data() + off;
+		r.flags       = static_cast<uint8_t>(*p);
+		++p;
+		memcpy(&r.id, p, sizeof(r.id));
+		p += sizeof(r.id);
+		memcpy(&r.next, p, sizeof(r.next));
+		p += sizeof(r.next);
+		memcpy(r.desc, p, 64);
+		p += 64;
+		memcpy(&r.delta, p, sizeof(r.delta));
+	}
+
+	void RuleRecord::clear(RuleRecord &r)
+	{
+		std::memset(r.desc, 0, sizeof(r.desc));
+		r.delta = 0;
+	}
+}
diff --git a/src/storage_management.ixx b/src/storage_management.ixx
new file mode 100644
index 0000000..34fc463
--- /dev/null
+++ b/src/storage_management.ixx
@@ -0,0 +1,405 @@
+// Storage Management Module for Points
+// copyright (c) 2026 SECTL, Licensed under GPL-3.0
+module;
+#include <array>
+#include <cstdint>
+#include <cstddef>
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <memory>
+#include <numeric>
+#include <stdexcept>
+#include <string>
+#include <string_view>
+#include <vector>
+#if defined(__cpp_impl_reflection)
+#include <meta>
+#endif
+
+export module storage;
+import exceptions;
+
+// 模块私有：CRC-32（IEEE 802.3，poly 0xEDB88320），覆盖整个记录区。
+// 定义在接口里（inline）：FileStore 模板在消费 TU 实例化时也要调用它
+namespace
+{
+	constexpr std::array<uint32_t, 256> make_crc32_table()
+	{
+		std::array<uint32_t, 256> t{};
+		for (uint32_t i = 0; i < 256; ++i)
+		{
+			uint32_t c = i;
+			for (int k = 0; k < 8; ++k)
+				c = (c & 1) ? (0xEDB88320u ^ (c >> 1)) : (c >> 1);
+			t[i] = c;
+		}
+		return t;
+	}
+
+	constexpr auto crc32_table = make_crc32_table();
+}
+
+inline uint32_t crc32(const void *data, size_t len)
+{
+	const auto *p = static_cast<const uint8_t *>(data);
+	uint32_t    c = 0xFFFFFFFFu;
+	for (size_t i = 0; i < len; ++i)
+		c = crc32_table[(c ^ p[i]) & 0xFF] ^ (c >> 8);
+	return c ^ 0xFFFFFFFFu;
+}
+
+export namespace points
+{
+	constexpr char     FILE_HEADER_MAGIC[4] = {'P', 'N', 'T', 'S'};
+	constexpr uint32_t FILE_VERSION         = 1;
+
+	struct FileHeader
+	{
+		char     magic[4];
+		uint32_t version;
+		uint32_t header_size;
+		uint32_t record_size;
+		uint32_t capacity;
+		uint32_t alive_count;
+		uint32_t free_head;
+		uint32_t crc32;
+	};
+
+	// 学生记录。磁盘布局见 get_record_field_size；flags bit0 = alive，
+	// 空闲槽借 next 挂空闲链表。序列化/清零由各记录类型自行提供。
+	struct Record
+	{
+		uint8_t   flags;
+		uint32_t  id;
+		uint32_t  next;
+		std::byte name[64];
+		std::byte gender[64];
+		int64_t   old_score;
+		int64_t   score;
+		uint32_t  old_rank;
+		uint32_t  rank;
+
+		static void put(std::string &buf, const Record &r); // 字段连续贴（161B，无 padding）
+		static void get(std::string_view s, size_t slot, Record &r);
+
+		static void          clear(Record &r); // 新增时字段清零
+		static consteval int disk_size();      // 磁盘记录长度
+	};
+
+	constexpr uint32_t file_header_size = sizeof(FileHeader);
+
+	// 磁盘记录长度 = 字段连续和（≠ sizeof(Record)，padding 不入文件）
+	// flags(1) id(4) next(4) name(64) gender(64) old_score(8) score(8) old_rank(4) rank(4) = 161
+	#if defined(__cpp_impl_reflection)
+	// P2996 反射（GCC 16.1+ -freflection）：字段尺寸由 Record 直接推导，数组/断言整体退役
+	consteval int get_record_field_size()
+	{
+		std::size_t    s   = 0;
+		constexpr auto ctx = std::meta::access_context::unchecked();
+		for (auto m: std::meta::nonstatic_data_members_of(^^Record, ctx))
+			s += std::meta::size_of(m);
+		return static_cast<int>(s);
+	}
+	#else
+	// 反射不可用（clang-cl/MSVC）时的替身：数组 + 逐字段断言
+	constexpr int record_field_sizes[9] = {1, 4, 4, 64, 64, 8, 8, 4, 4};
+	// 数组 ↔ 结构体逐字段钉死：改 Record 忘了数组，编译报错而不是运行时错位
+	static_assert(sizeof(Record::flags) == record_field_sizes[0]);
+	static_assert(sizeof(Record::id) == record_field_sizes[1]);
+	static_assert(sizeof(Record::next) == record_field_sizes[2]);
+	static_assert(sizeof(Record::name) == record_field_sizes[3]);
+	static_assert(sizeof(Record::gender) == record_field_sizes[4]);
+	static_assert(sizeof(Record::old_score) == record_field_sizes[5]);
+	static_assert(sizeof(Record::score) == record_field_sizes[6]);
+	static_assert(sizeof(Record::old_rank) == record_field_sizes[7]);
+	static_assert(sizeof(Record::rank) == record_field_sizes[8]);
+
+	consteval int get_record_field_size()
+	{
+		return std::accumulate(
+			std::begin(record_field_sizes),
+			std::end(record_field_sizes),
+			0);
+	}
+	#endif
+	// 两条路径都必须得出同一个磁盘长度，编译期钉死
+	static_assert(get_record_field_size() == 161, "record disk size");
+
+	consteval int Record::disk_size()
+	{
+		return get_record_field_size();
+	}
+
+	// 规则/礼物记录：desc 描述 + delta 分值，77B = 1+4+4+64+4
+	struct RuleRecord
+	{
+		uint8_t   flags;
+		uint32_t  id;
+		uint32_t  next;
+		std::byte desc[64];
+		int32_t   delta;
+
+		static void put(std::string &buf, const RuleRecord &r);
+
+		static void get(std::string_view s, size_t slot, RuleRecord &r);
+
+		static void clear(RuleRecord &r);
+
+		static consteval int disk_size();
+	};
+
+	#if defined(__cpp_impl_reflection)
+	consteval int rule_disk_size()
+	{
+		std::size_t    s   = 0;
+		constexpr auto ctx = std::meta::access_context::unchecked();
+		for (auto m: std::meta::nonstatic_data_members_of(^^RuleRecord, ctx))
+			s += std::meta::size_of(m);
+		return static_cast<int>(s);
+	}
+	#else
+	constexpr int rule_field_sizes[5] = {1, 4, 4, 64, 4};
+	static_assert(sizeof(RuleRecord::flags) == rule_field_sizes[0]);
+	static_assert(sizeof(RuleRecord::id) == rule_field_sizes[1]);
+	static_assert(sizeof(RuleRecord::next) == rule_field_sizes[2]);
+	static_assert(sizeof(RuleRecord::desc) == rule_field_sizes[3]);
+	static_assert(sizeof(RuleRecord::delta) == rule_field_sizes[4]);
+
+	consteval int rule_disk_size()
+	{
+		return std::accumulate(
+			std::begin(rule_field_sizes),
+			std::end(rule_field_sizes),
+			0);
+	}
+	#endif
+	static_assert(rule_disk_size() == 77, "rule disk size");
+
+	consteval int RuleRecord::disk_size()
+	{
+		return rule_disk_size();
+	}
+
+	// 礼物 = 规则同构（desc + delta），独立类型以让 FileStore 各自持有单例
+	struct GiftRecord : RuleRecord
+	{
+		static consteval int disk_size()
+		{
+			return rule_disk_size();
+		}
+	};
+
+	// 空闲链表哨兵：0xFFFFFFFF = 无空闲槽
+	inline constexpr uint32_t FREE_NIL = 0xFFFFFFFFu;
+
+	// 通用定长记录文件引擎：header(32B) + 记录数组，空闲链表复用，CRC 校验，
+	// tmp+rename 原子写。每记录类型一个单例（FileStore<T>::get()）。
+	// T 必须提供：put/get/clear/disk_size。
+	template<typename T>
+	class FileStore
+	{
+	public:
+		static void init(const std::filesystem::path &path)
+		{
+			if (inst_) throw std::logic_error("FileStore already initialized");
+			inst_ = std::unique_ptr<FileStore>(new FileStore(path));
+		}
+
+		static FileStore &get()
+		{
+			if (!inst_) throw std::logic_error("FileStore not initialized");
+			return *inst_;
+		}
+
+		FileStore(const FileStore &) = delete;
+
+		FileStore &operator=(const FileStore &) = delete;
+
+		const std::filesystem::path &path() const noexcept
+		{
+			return file_path_;
+		}
+
+		void load()
+		{
+			if (!std::filesystem::exists(file_path_))
+			{ // 全新表：空库
+				pool_.clear();
+				capacity_  = 0;
+				alive_     = 0;
+				free_head_ = FREE_NIL;
+				loaded_    = true;
+				dirty_     = false;
+				return;
+			}
+			std::ifstream fin(file_path_, std::ios::binary);
+			fin.exceptions(std::ios::failbit | std::ios::badbit);
+			FileHeader h;
+			fin.read(reinterpret_cast<char *>(&h), sizeof(h));
+			if (std::memcmp(h.magic, FILE_HEADER_MAGIC, 4) != 0)
+				throw file_format_error("文件头魔数不符");
+			if (h.version != FILE_VERSION)
+				throw file_format_error("文件版本不符");
+			if (h.header_size != file_header_size || h.record_size != static_cast<
+				uint32_t>(T::disk_size()))
+				throw file_format_error("文件头尺寸字段不符");
+
+			std::string buf(static_cast<size_t>(h.record_size) * h.capacity, '\0');
+			fin.read(buf.data(), static_cast<std::streamsize>(buf.size()));
+			if (crc32(buf.data(), buf.size()) != h.crc32)
+				throw file_format_error("记录区 CRC 校验失败");
+
+			pool_.resize(h.capacity);
+			uint32_t chain = FREE_NIL;
+			uint32_t tail  = FREE_NIL;
+			uint32_t alive = 0;
+			for (uint32_t i = 0; i < h.capacity; ++i)
+			{
+				T::get(buf, i, pool_[i]);
+				if (pool_[i].flags & 0x01u)
+				{
+					++alive;
+					pool_[i].next = 0;
+				}
+				else
+				{
+					pool_[i].next = FREE_NIL;
+					if (tail == FREE_NIL) chain = i;
+					else pool_[tail].next       = i;
+					tail = i;
+				}
+			}
+			if (alive != h.alive_count)
+				throw file_format_error("存活计数与文件头不符");
+			uint32_t n = 0;
+			for (uint32_t s = chain; s != FREE_NIL; s = pool_[s].next)
+				if (++n > h.capacity)
+					throw file_format_error("空闲链表成环或超长");
+			if (n != h.capacity - h.alive_count)
+				throw file_format_error("空闲链表长度不符");
+
+			capacity_  = h.capacity;
+			alive_     = alive;
+			free_head_ = chain;
+			loaded_    = true;
+			dirty_     = false;
+		}
+
+		void save()
+		{
+			if (!dirty_) return; // 懒写：无变更不落盘
+			FileHeader h;
+			std::memcpy(h.magic, FILE_HEADER_MAGIC, 4);
+			h.version     = FILE_VERSION;
+			h.header_size = file_header_size;
+			h.record_size = static_cast<uint32_t>(T::disk_size());
+			h.capacity    = capacity_;
+			h.alive_count = alive_;
+			h.free_head   = free_head_;
+			h.crc32       = 0;
+
+			std::string buf;
+			buf.reserve(
+				file_header_size + static_cast<size_t>(capacity_) * T::disk_size());
+			buf.append(reinterpret_cast<const char *>(&h), sizeof(h));
+			for (uint32_t i = 0; i < capacity_; ++i)
+				T::put(buf, pool_[i]);
+			h.crc32 = crc32(buf.data() + sizeof(h), buf.size() - sizeof(h));
+			std::memcpy(buf.data(), &h, sizeof(h));
+
+			auto tmp = file_path_;
+			tmp      += ".tmp";
+			{
+				std::ofstream fout(tmp, std::ios::binary);
+				fout.exceptions(std::ios::failbit | std::ios::badbit);
+				fout.write(buf.data(), static_cast<std::streamsize>(buf.size()));
+			}
+			std::filesystem::rename(tmp, file_path_); // 原子替换
+			dirty_  = false;
+			loaded_ = true;
+		}
+
+		void switch_to(const std::filesystem::path &path)
+		{
+			if (path == file_path_) return;
+			save(); // 脏则先落盘当前表
+			file_path_ = path;
+			loaded_    = false;
+			load();
+		}
+
+		T *find(uint32_t id) noexcept
+		{
+			if (id >= capacity_) return nullptr;
+			auto &r = pool_[id];
+			return (r.flags & 0x01u) ? &r : nullptr;
+		}
+
+		const T *find(uint32_t id) const noexcept
+		{
+			if (id >= capacity_) return nullptr;
+			const auto &r = pool_[id];
+			return (r.flags & 0x01u) ? &r : nullptr;
+		}
+
+		uint32_t add()
+		{
+			uint32_t slot;
+			if (free_head_ != FREE_NIL)
+			{ // 复用空洞（LIFO）
+				slot       = free_head_;
+				free_head_ = pool_[slot].next;
+			}
+			else
+			{ // 追加新槽
+				slot = capacity_++;
+				pool_.emplace_back();
+			}
+			auto &r = pool_[slot];
+			r.flags = 0x01u;
+			r.id    = slot;
+			r.next  = 0;
+			T::clear(r);
+			++alive_;
+			dirty_ = true;
+			return slot;
+		}
+
+		bool remove(uint32_t id)
+		{
+			auto *r = find(id);
+			if (!r) return false;
+			r->flags   = 0; // 借 next 挂空闲链
+			r->next    = free_head_;
+			free_head_ = id;
+			--alive_;
+			dirty_ = true;
+			return true;
+		}
+
+		void mark_dirty() noexcept
+		{
+			dirty_ = true;
+		}
+
+	private:
+		explicit FileStore(std::filesystem::path path) : file_path_(std::move(path))
+		{}
+
+		std::filesystem::path             file_path_;
+		std::vector<T>                    pool_;
+		uint32_t                          capacity_  = 0;
+		uint32_t                          alive_     = 0;
+		uint32_t                          free_head_ = FREE_NIL;
+		bool                              loaded_    = false;
+		bool                              dirty_     = false;
+		static std::unique_ptr<FileStore> inst_;
+	};
+
+	template<typename T>
+	std::unique_ptr<FileStore<T> > FileStore<T>::inst_;
+
+	// 学生表门面（保持既有 API/单例语义）
+	using FileInstance = FileStore<Record>;
+}
diff --git a/xmake.lua b/xmake.lua
index 12e879c..1f79758 100644
--- a/xmake.lua
+++ b/xmake.lua
@@ -7,14 +7,21 @@ local APP_VERSION = "1.0.0"
 set_version(APP_VERSION)
 
 set_languages("c++20")
-add_requires("nlohmann_json","libcurl")
+-- gcc 工具链下不声明包：xmake 包缓存会把 MSVC 编译的 libcurl 串给 gcc（__GSHandlerCheck 链接失败），
+-- 且 points_modernize（模块/反射目标）不用任何包；old 目标保持 clang-cl 编译
+if get_config("toolchain") ~= "gcc" then
+    add_requires("nlohmann_json","libcurl")
+end
 
 target("points_main")
     set_kind("binary")
     add_files("old/main.cpp")
-    add_packages("nlohmann_json", "libcurl")
+    if get_config("toolchain") ~= "gcc" then
+        add_packages("nlohmann_json", "libcurl")
+    end
     add_defines(string.format('APP_VERSION="%s"', APP_VERSION))
-    if is_plat("windows") then
+    -- /utf-8 是 MSVC/clang-cl 专用；GCC 默认按 UTF-8 读源码
+    if get_config("toolchain") ~= "gcc" then
         add_cxflags("/utf-8", {force = true})  -- main.cpp 是 UTF-8 无 BOM，MSVC 默认按 GBK 读
     end
 
@@ -23,3 +30,21 @@ target("points_main")
 
     -- 2. 生成的 rc 不会自动编译，必须手动加进源文件
     add_files("$(builddir)/version.rc", {always_added = true})
+
+target("points_modernize")
+    set_kind("binary")
+    add_files("src/storage_management.ixx")
+    add_files("src/exceptions.ixx")
+    add_files("src/storage_management.cpp")
+    add_files("src/data_storage.cpp")
+    add_includedirs("include")
+    add_defines(string.format('APP_VERSION="%s"', APP_VERSION))
+    -- /utf-8 是 MSVC/clang-cl 专用；GCC 默认按 UTF-8 读源码
+    if get_config("toolchain") ~= "gcc" then
+        add_cxflags("/utf-8", {force = true})
+    end
+    -- GCC 16.1+ 启用 C++26 反射（P2996，-freflection）；源码侧用 __cpp_impl_reflection 自动检测
+    -- 注：xmake 3.0 DSL 无进程执行/版本探测 API，不解析 gcc 版本；gcc < 16 会因 -freflection 明确报错
+    if get_config("toolchain") == "gcc" then
+        add_cxflags("-std=c++26", "-freflection", {force = true})
+    end
